微信公众号支付

对开发测试成功后的源码以及在开发中遇到的问题做一下简要说明

 开发之前的准备工作:

    参数:开发之前你要知道请求每个接口地址的参数所代表的含义  //下面四个是必须知道的参数

 

       appid  //公众号appid

         appsecret //公众号密钥

       mch_id //商户id

       key  //商户密钥

       appid和appsecret 在微信公众号中获取,mch_id和key在公众号对应的微信商户号中获取

    设置支付授权目录

    开发之前先要熟悉一下业务流程 

      业务流程时序图

微信公众号支付

  开发流程可以分为三步:

  1, 授权,获取openid

  2, 调用统一下单API,获取预支付信息

  3, 在页面调用微信支付js并且完成支付

 

  一, 获取openid

  1,  授权

      调用微信OAuth2.0网页授权

    类 : GetCodeServlet.java  //getCode

public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        
        String appid = "";   //公众号id
        String state = "0";
        String scope = "snsapi_base";
        String redirect_uri = "http://cmy.tunnel.qydev.com/wx_cmy/pay";  //重定向的url,就是授权后要跳转的地址
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appid+"&redirect_uri="+redirect_uri+"&response_type=code&scope="+scope+"&state="+state+"#wechat_redirect";
        //System.out.println("code === "+ url.toString());
        response.sendRedirect(url.toString());
    }

 

  scope:应用授权作用域

          snsapi_base:不弹出授权页面,直接跳转,只能获取用户openid

          snsapi_userinfo:弹出授权页面,可通过openid拿到昵称、性别、所在地

 

  2, 获取用户微信OpenId

  类:PayServlet.java  //pay   重定向之后的url

public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 获取openid
         */
    
        String appid = "";
        String appsecret = "";
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appid+"&secret="+secret+"&code="+code+"&grant_type=authorization_code";  //接口地址
        
        //System.out.println("pay code === "+ url.toString());
        
        String requestMethod = "GET";
        String outputStr = "";
        String httpRequest = WeixinUtil.httpRequest(url, requestMethod, outputStr); 
        JSONObject obj = JSONObject.fromObject(httpRequest);
        String openid = obj.get("openid").toString();
        //System.out.println("openid是"+openid);
        

  

   WeixinUtil.httpRequest(url, requestMethod, outputStr);

/**
     * 发起https请求并获取结果
     * 
     * @param requestUrl 请求地址
     * @param requestMethod 请求方式(GET、POST)
     * @param outputStr 提交的数据
     * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
     */
    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
        StringBuffer buffer = new StringBuffer();
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
            httpUrlConn.setSSLSocketFactory(ssf);

            httpUrlConn.setDoOutput(true);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            httpUrlConn.setRequestMethod(requestMethod);

            if ("GET".equalsIgnoreCase(requestMethod))
                httpUrlConn.connect();

            // 当有数据需要提交时
            if (null != outputStr) {
                OutputStream outputStream = httpUrlConn.getOutputStream();
                // 注意编码格式,防止中文乱码
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 将返回的输入流转换成字符串
            InputStream inputStream = httpUrlConn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            bufferedReader.close();
            inputStreamReader.close();
            // 释放资源
            inputStream.close();
            inputStream = null;
            httpUrlConn.disconnect();
            
        } catch (ConnectException ce) {

            System.out.println("---CMY---Weixin server connection timed out.");
        } catch (Exception e) {

            System.out.println("---CMY---https request error:{}");
        }
        return buffer.toString();
    }

 

 

   二, 调用统一下单API,获取预支付信息

      在得到openid之后,调用统一下单接口

      通过openid获取prepay_id 

//-------------统一下单---------------
        /**
         *  预支付
         */
        RequestHandler reqHandler = new RequestHandler(request, response);
        reqHandler.init(com.bean.Constants.appid, com.bean.Constants.appsecret, com.bean.Constants.key);
        //获取openId后调用统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder
                String requestXml="";
                try {
                    requestXml = CommonUtil.getSign(request, response,reqHandler, request.getRemoteAddr(), openid);   //获取 请求参数 的xml,
          
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                //.out.println(requestXml);
                
                 
                String createURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";   //统一下单接口url
    
                String prepay_id="";
                try {
                    prepay_id = new GetWxOrderno().getPayNo(createURL, requestXml);  //获取 prepay_id
                    if(prepay_id.equals("")){
                        System.out.println("----------- 你好,我是分割线 ------------");
                        response.sendRedirect("error.jsp");
                    }
                } catch (Exception e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                System.out.println("--------------------- 超级华丽分割线 -----------------------");
                System.out.println("订单id是"+prepay_id);                

CommonUtil.getSign(request, response,reqHandler, request.getRemoteAddr(), openid);
在这个方法里,生成请求参数的签名,并把签名放到请求参数拼接成的xml里
/**
     *  请求参数
     * @throws Exception
     */
    public static String getSign(HttpServletRequest request, HttpServletResponse response ,RequestHandler reqHandler,String ip,String openId) throws Exception {
        
        String appid = "";
        String mch_id= "";  //商户id
        
        String nonce_str =  getNonce_str(); //随机字符串
        
        //商户订单号   自己定义
        String out_trade_no = "A"+TenpayUtil.getCurrTime();
    
        String notify_url  ="http://cmy.tunnel.qydev.com/wx_cmy/notify";  //接收微信支付异步通知回调地址
        
        String trade_type = "JSAPI";
        String openid = openId; //上面取到的openid
        
        
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        packageParams.put("appid", appid);  
        packageParams.put("mch_id", mch_id);  
        packageParams.put("nonce_str", nonce_str);  
        packageParams.put("body", "my_xn_iphone");  
        packageParams.put("attach", "WeiXin_zf");  
        packageParams.put("out_trade_no", out_trade_no);  
        //这里写的金额为1 分到时修改
        packageParams.put("total_fee", "1");   
        packageParams.put("spbill_create_ip", ip);  
        packageParams.put("notify_url", notify_url);  
        packageParams.put("trade_type", trade_type);  
        packageParams.put("openid", openid);  

        String sign = reqHandler.createSign(packageParams);  //通过map获取签名,这块也可以把参数拼接成字符串 之后进行sign=MD5(stringSignTemp).toUpperCase();生成签名,可以参照开发文档--签名算法 
    
        //生成请求参数的xml,这个也可以调用字符串或map【前提是 把刚生成的sign已经添加到字符串里或添加到map里了】转成xml的方法生成xml
        String xml="<xml>"+
                "<appid>"+appid+"</appid>"+
                "<mch_id>"+mch_id+"</mch_id>"+
                "<nonce_str>"+nonce_str+"</nonce_str>"+
                "<sign>"+sign+"</sign>"+
                "<body><![CDATA[my_xn_iphone]]></body>"+
                "<attach>WeiXin_zf</attach>"+
                "<out_trade_no>"+out_trade_no+"</out_trade_no>"+
                //金额,这里写的1 分到时修改
                "<total_fee>"+1+"</total_fee>"+
                //"<total_fee>"+finalmoney+"</total_fee>"+
                "<spbill_create_ip>"+ip+"</spbill_create_ip>"+
                "<notify_url>"+notify_url+"</notify_url>"+
                "<trade_type>"+trade_type+"</trade_type>"+
                "<openid>"+openid+"</openid>"+
                "</xml>";
       return xml;
    }                

createSign(SortedMap<String, String> packageParams);
 1 /**
 2      * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
 3      */
 4     public String createSign(SortedMap<String, String> packageParams) {
 5         StringBuffer sb = new StringBuffer();
 6         Set es = packageParams.entrySet();
 7         Iterator it = es.iterator();
 8         while (it.hasNext()) {
 9             Map.Entry entry = (Map.Entry) it.next();
10             String k = (String) entry.getKey();
11             String v = (String) entry.getValue();
12             if (null != v && !"".equals(v) && !"sign".equals(k)
13                     && !"key".equals(k)) {
14                 sb.append(k + "=" + v + "&");
15             }
16         }
17         sb.append("key=" + this.getKey());
18         System.out.println("md5 sb:" + sb+"key="+this.getKey());
19         String sign = MD5Util.MD5Encode(sb.toString(), this.charset)
20                 .toUpperCase();
21         System.out.println("签名:" + sign);
22         return sign;
23 
24     }

 

   三, 在页面调用微信支付js并且完成支付

       获取到prepay_id,

 SortedMap<String, String> finalpackage = new TreeMap<String, String>();
                 String appid = ""; //不用写你应该也是到是啥了
                 String timestamp = Sha1Util.getTimeStamp();
                 String nonceStr2 = CommonUtil.getNonce_str();
                 String prepay_id2 = "prepay_id="+prepay_id;     //拼接packages
                 String packages = prepay_id2;
                 finalpackage.put("appId", appid);  
                 finalpackage.put("timeStamp", timestamp);  
                 finalpackage.put("nonceStr", nonceStr2);  
                 finalpackage.put("package", packages);  
                 finalpackage.put("signType", "MD5");
                 String finalsign = reqHandler.createSign(finalpackage); //生成携有以上参数的签名
                 //请求前端页面调用微信支付js并完成支付
                 response.sendRedirect("pay.jsp?appid="+appid2+"&timeStamp="+timestamp+"&nonceStr="+nonceStr2+"&package="+packages+"&sign="+finalsign);
     }

     pay.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String appId = request.getParameter("appid");
String timeStamp = request.getParameter("timeStamp");
String nonceStr = request.getParameter("nonceStr");
String packageValue = request.getParameter("package");
String paySign = request.getParameter("sign");
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>微信支付</title>
    
    <meta name="viewport" content="width=device-width; initial-scale=1.0">  //h5适应手机
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->

  </head>
  
  <body>
        <br><br><br>
              
        <div style="text-align:center;">
                <h2>xxx __ 微信支付 </h2>
                <h3>价格: 0.01元</h3>
        </div>
        <div style="text-align:center;">
            <input type="button" value="确认支付" onclick="callpay()">
        </div>
  </body>
  <script type="text/javascript">
      function callpay(){
         WeixinJSBridge.invoke(‘getBrandWCPayRequest‘,{
           "appId" : "<%=appId%>",
           "timeStamp" : "<%=timeStamp%>", 
           "nonceStr" : "<%=nonceStr%>",
           "package" : "<%=packageValue%>",
           "signType" : "MD5", 
           "paySign" : "<%=paySign%>" 
               },function(res){
                WeixinJSBridge.log(res.err_msg);
                if(res.err_msg == "get_brand_wcpay_request:ok"){  
                    alert("微信支付成功!");
                    
                }else if(res.err_msg == "get_brand_wcpay_request:cancel"){  
                    alert("用户取消支付!");  
                    
                }else{  
                   alert("支付失败!");  
                   
                }  
            })
        }
  </script>
  
</html>

 

    生成时间戳以及随即字符串的方法

 public static String getTimeStamp() {
         return String.valueOf(System.currentTimeMillis() / 1000);
     }
     
 
 public static String getNonce_str(){
 
         String currTime = TenpayUtil.getCurrTime();
         //8位日期
         String strTime = currTime.substring(8, currTime.length());
         //四位随机数
         String strRandom = TenpayUtil.buildRandom(4) + "";
         //10位序列号,可以自行调整。
         String str = strTime + strRandom;
         
         return str ;
         
     }

   开发中遇到的问题:

     这个就不得不吐槽一下微信支付的开发文档了,返回错误信息太笼统,小坑无数,说多都是泪啊

     开发中值得注意的几点:

    1,所有签名中携带的key都是商户密钥

    2,如果你的签名通过了微信支付接口签名校验工具校验成功 ,那么你就别怀疑了,重新申请一个商户密钥 //我就静静地在这个坑里呆了三天

    3,如果你的签名未通过了微信支付接口签名校验工具校验,那你就检查一下,你参与签名的参数是否按参数名ASCII码未按升序排列,或者是生成MD5字符串没有             toUpperCase转换为大写

    

  ***** 以上就是我对微信公众号支付开发的总结,希望能对您有所帮助,再次感谢您的阅读 *****

微信公众号支付

上一篇:微信调用照相拍照等 js 接口的权限配置 和 照片上传和下载实现


下一篇:微微信.NET 为什么採用文件系统而不是数据库?