微信公众号支付(h5/java踩坑)

语言:java、html
前端H5 后端SpringMVC
  • 申请公众号商户号和开发者平台账号这个就不用说了吧。
    拿到参数有三个:

    参数名 描述
    appid 公众账号ID
    mch_id 商户号
    secret 公众号秘钥
  • 统一下单需要参数示例(官方给出示例)

<xml>
   <appid>wx2421b1c4370ec43bappid>
   <attach>支付测试attach>
   <body>JSAPI支付测试body>
   <mch_id>10000100mch_id>
   <detail>detail>
   <nonce_str>1add1a30ac87aa2db72f57a2375d8fecnonce_str>
   <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.phpnotify_url>
   <openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6oopenid>
   <out_trade_no>1415659990out_trade_no>
   <spbill_create_ip>14.23.150.211spbill_create_ip>
   <total_fee>1total_fee>
   <trade_type>JSAPItrade_type>
   <sign>0CB01533B8C1EF103065174F50BCA001sign>
xml>
  • 获取openid(已经获取到openid可以略过此步骤)
    统一下单接口需要传输的参数中需要openid,但是公众号中只有code参数所有我们要先获取openid.
    前端js代码
var url="<%=path%>XXX/wechat/pay.do?orderId="+orderId; //这里不可以带端口号:80这种,不然前端会报10003错误
var weixinUrl="https://open.weixin.qq.com/connect/oauth2/authorize?appid=app_id&redirect_uri="+url+"&response_type=code&scope=snsapi_userinfo&state="+orderId+"#wechat_redirect";
window.location.href=encodeURI(weixinUrl);

url是你的地址可以是获取openid的接口,也可以是直接下单接口,如果是直接下单的接口那么获取openid就的和生成预订单时一起获取.

后端获取openid

public List<Object> accessToken(String code) throws IOException {
    List<Object> list = new ArrayList<Object>();
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
            + WeChat.APPID + "&secret=" + WeChat.secret + "&code="
            + code + "&grant_type=authorization_code";
    HttpClient client = new DefaultHttpClient();
    HttpPost post = new HttpPost(url);
    HttpResponse res = client.execute(post);
    if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
        HttpEntity entity = res.getEntity();
        String str = org.apache.http.util.EntityUtils.toString(entity,
                "utf-8");
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> jsonOb = mapper.readValue(str, Map.class);
        list.add(jsonOb.get("access_token"));
        list.add(jsonOb.get("openid"));
    }
    return list;
}

获取到access_token和openid

  • 统一下单接口
    url:https://api.mch.weixin.qq.com/pay/unifiedorder

        String nonce_str = MD5Util.MD5Encode(String.valueOf(SnowflakeIdUtil.getSnowflakeId()), "UTF-8");
        // 订单生成的机器 IP
        String spbill_create_ip = request.getRemoteAddr();
        // 交易类型 :jsapi代表微信公众号支付
        String trade_type = "JSAPI";
        // 这里notify_url是 微信处理完支付后的回调的应用系统接口url。
        String url= request.getRequestURL().toString().substring(0, url.lastIndexOf("/")+1);
        //生产环境
        String notify_url= url+"weixinNotify.do";
    
        //组装参数
        SortedMap packageParams = new TreeMap();
        packageParams.put("appid", "公众账号ID");  
        packageParams.put("body", "JSAPI支付测试");  
        packageParams.put("mch_id",  "商户号");
        packageParams.put("detail",  "商品描述");
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("notify_url", notify_url);  
        packageParams.put("openid", openid); //openid
        packageParams.put("out_trade_no", out_trade_no);//订单号
        packageParams.put("spbill_create_ip", spbill_create_ip);
        packageParams.put("total_fee", finalmoney);  //金额数目,这里的需要转换成分,而不是元
        packageParams.put("trade_type", trade_type); 
        packageParams.put("device_info", "WEB");
        String sign = PayCommonUtil.createSign("UTF-8",packageParams);
        packageParams.put("sign", sign);

    这里我用的是自己写的签名方法,官方Demo中有生成签名的方法

public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        List<String> list = new ArrayList<String>();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            list.add(k);
        }
        Collections.sort(list);
        for(String str : list){
            Object v = parameters.get(str);
            if(null != v && !"".equals(v) && !"sign".equals(str) && !"key".equals(str)) {
              sb.append(str + "=" + v + "&");
            }
        }
        sb.append("key="+"这里是开发配置的key");//需要自己生成并填写到开发配置中
        String stringSignTemp = sb.toString();
        String sign = MD5Util.MD5Encode(stringSignTemp, characterEncoding).toUpperCase();
        return sign;
    }

生成xml并请求预订单

        String requestXML = PayCommonUtil.getRequestXml(packageParams); //将Map集合转换为XMl
        String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String result = PayCommonUtil.httpsRequest(createOrderURL, "POST", requestXML);
        Map<String, String> resultMap = XMLUtil.doXMLParse(result);//将XML转换为Map集合
        String  prepay_id = resultMap .get("prepay_id");

文中出现的工具类官方demo中基本都有,没有的话自己写一个也就好了
如果能获取到prepay_id 那么你就基本上可以happy一下了
组装调起微信支付的参数列表

    SortedMap finalpackage = new TreeMap();
    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳
    String packages = "prepay_id="+prepay_id;//这里需要特别注意下,package是关键字,尴尬!-_-
    finalpackage.put("appId",  WeChat.GZHAPPID);//这里注意一下,appId是大写的I
    finalpackage.put("timeStamp", timestamp);
    finalpackage.put("nonceStr", nonce_str);  
    finalpackage.put("package", packages);  
    finalpackage.put("signType", "MD5");

    Map mv = new HashedMap<>();
    mv.put("appId",  WeChat.GZHAPPID);
    mv.put("timeStamp", timestamp);
    mv.put("nonceStr", nonce_str);
    mv.put("packageStr", packages);
    mv.put("signType", "MD5");
    mv.put("paySign", PayCommonUtil.createSign("UTF-8",finalpackage));//获取二次签名
    mv.put("success","ok");
    mv.put("orderId",out_trade_no);
    mv.put("payPrice",payPrice);

ok
返回前端唤起微信支付吧

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

支付成功后回调

@RequestMapping(value="/weixinNotify.do")
public void weixinNotify(HttpServletRequest request, HttpServletResponse response){
    String out_trade_no=null;
    String return_code =null;
    try {
        InputStream inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        String resultStr  = new String(outSteam.toByteArray(),"utf-8");
        logger.info("支付成功的回调:"+resultStr);
        Map<String, Object> resultMap = XMLUtil.doXMLParse(resultStr);
        //下面这些参数需要的可以获取,不需要也可以不用获取
        String result_code = (String) resultMap.get("result_code");
        String is_subscribe = (String) resultMap.get("is_subscribe");
        String transaction_id = (String) resultMap.get("transaction_id");
        String sign = (String) resultMap.get("sign");
        String time_end = (String) resultMap.get("time_end");
        String bank_type = (String) resultMap.get("bank_type");
        out_trade_no = (String) resultMap.get("out_trade_no");
        return_code = (String) resultMap.get("return_code");

        request.setAttribute("out_trade_no", out_trade_no);
        //通知微信.异步确认成功.必写.不然微信会一直通知后台.八次之后就认为交易失败了.
        response.getWriter().write(RequestHandler.setXML("SUCCESS", ""));
    }  catch (Exception e) {
        logger.error("微信回调接口出现错误:",e);
        try {
            response.getWriter().write(RequestHandler.setXML("FAIL", "error"));
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    } 
    if(return_code.equals("SUCCESS")){
        //支付成功的业务逻辑
    }else{
        //支付失败的业务逻辑
    }
}

微信公众号官方文档大家可以看看,也可以吐槽。
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

微信官方给出参数的详细说明。
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

如果文中有什么不对的,或者有什么问题,可以直接留言.欢迎指正!

你可能感兴趣的:(java,支付,html)