1. 程式人生 > >微信掃碼支付 模式一 (JSAPI)

微信掃碼支付 模式一 (JSAPI)

這個微信支付是靜態二維碼支付,就是店面貼著一個二維碼,讓消費者自己掃自己輸入金額,自己發起支付的支付方式。

要準備的東西比較麻煩:
1、到微信公眾號平臺設定Oauth2的網頁驗證域名(用於獲取code,code用於拿到發起支付的openId),格式是www.xxxx.com/file1/file2/,不需要https:// 要精確到發起支付頁面的當前路徑
2、配置Oauth2網頁驗證域名的時候,需要下載一個txt,放到發起支付頁面的統計目錄
3、到微信公眾號平臺還是商戶平臺設定支付授權地址(用於發起支付),不會設定可以發郵件到微信工作人員郵箱,申請處理,郵箱要自己挖(這個地址需要有https://)
4、拿到微訊號的appid、mch_id、appscret、key、退款證書、sub_mch_id(服務商需要為子商戶開這個,而前五個都是用服務商自己的就可以了)
5、對於境內微信商戶,Oauth2的網頁驗證域名和支付授權地址必須是通過了國內的ICP備案,不然不能用,對於境外微信商戶則沒有這個要求

以上四個東西都拿到做好了 就可以開始了 (開發文件無敵坑)

一、首先寫個通過Oauth的驗證的頁面(注意redirect_url需要urlencode)
jsapi1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*"
%>
<%@ page language="java" import="java.net.URLDecoder" %> <%@ page language="java" import="org.json.JSONObject" %> <% request.setCharacterEncoding("UTF-8"); String currCode = request.getParameter("currCode"); String oauth2_url = ""; String wechat_appid = "資料庫讀取出來比較好"; String wechat_appsecret = "資料庫讀取出來比較好"
; String currCode = "RMB"; String state = currCode+"|"+wechat_appid+"|end"; if(wechat_appid==""){ System.out.println("no wechat appid"); }else{ System.out.println("WECHATappid:"+wechat_appid); } String redirect_url = "https://www.myserver.com/jsapi2.jsp"; redirect_url = URLEncoder.encode(redirect_url, "UTF-8"); oauth2_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+wechat_appid+"&redirect_uri="+redirect_url+"&response_type=code&scope=snsapi_base&state="+state+"#wechat_redirect"; %>
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title><fmt:message key="pg.eng.payment.payWECHATONL.text.title"/></title> </head> <body> <!-- this page will direct to wechat authorize url and then wechat will redirect to redirect_url with code and state --> <script language="JavaScript" type="text/javascript"> window.location.href="<%=oauth2_url%>"; </script> </body> </html>

二、然後微信會根據上一個頁面的redirect_url跳轉到對應頁面並給出一個openId,在這個頁面可以完成輸入金額的操作(注意要驗證是否微信瀏覽器開啟這個頁面,js裡面那個is_weixin();方法)
jsapi2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%@ page language="java" import="java.net.URLDecoder" %>
<%@ page language="java" import="org.json.JSONObject" %>
<%
request.setCharacterEncoding("UTF-8");
String openId = "";
String code = request.getParameter("code");
String state = request.getParameter("state");
System.out.println("code:"+code+",state:"+state);
String[] statespilt = state.split("\\|");
String currCode = statespilt[0];
String wechat_appid = statespilt[1];
String wechat_appsecret = "用wechat_appid查資料庫讀取出來比較好";
String notify_url = "回撥地址";
System.out.println("wechat_appid:"+wechat_appid+",wechat_appsecret:"+wechat_appsecret);
String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+wechat_appid+"&secret="+wechat_appsecret+"&code="+code+"&grant_type=authorization_code";
System.out.println("access_token_url:"+access_token_url);

String accesscode_rs = "";

accesscode_rs = HttpUtil.postData("",access_token_url);
System.out.println("accesscode_rs"+accesscode_rs);

JSONObject json;
json = new JSONObject(accesscode_rs);
System.out.println("OTTO-------set JSONObject");
openId = json.getString("openid");
System.out.println("OTTO-------openid:"+openId);
%>

<!-- ******************************** input page start *************************************** -->
<html>
    <head>
        <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
        <meta http-equiv="Pragma" content="no-cache">
        <meta http-equiv="Expires" content="0">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="Content-Style-Type" content="text/css">
        <script language="JavaScript" type="text/javascript">
            window.onload=function(){
                is_weixin();
            }   

            function is_weixin(){
                var ua = navigator.userAgent.toLowerCase();
                if(ua.match(/MicroMessenger/i)=="micromessenger") {

                } else {
                    window.location.href="remind.jsp";
                    return;
                }
            }
        </script>
        <title>
            input page
        </title>
        </head>
    <body>
    <form action="jsapi3.jsp" method="POST" >
        <input type="text" id="amount" name="amount" placeholder="input amount">
        <input type="submit" id="submit" name="submit" value="submit">
        <input type="hidden" id="currCode" name="currCode" value="<%=currCode %>">
        <input type="hidden" id="notify_url" name="notify_url" value="<%=notify_url %>">
        <input type="hidden" id="wechat_appid" name="wechat_appid" value="<%=wechat_appid %>">
    </form>
    </body>
</html>

三、根據上一個頁面獲取到的openId和金額、貨幣種類等引數呼叫統一訂單API,然後獲得一個prepay_id,用這個prepay_id放到發起支付的js裡面,微信客戶端就能跳出輸入密碼確認支付的框了,呼叫微信瀏覽器裡面的js方法來發起支付(注意發起支付的js方法,只有在微信瀏覽器裡面才生效)
jsapi3.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%@ page language="java" import="java.net.URLDecoder" %>
<%@ page language="java" import="org.json.JSONObject" %>
<%
request.setCharacterEncoding("UTF-8");
String wechat_appid = request.getParameter("wechat_appid")==null?"":request.getParameter("wechat_appid") ;
String openid = request.getParameter("openid")==null?"":request.getParameter("openid") ;
String amount = request.getParameter("amount")==null?"0":request.getParameter("amount") ;
String currCode = request.getParameter("currCode")==null?"":request.getParameter("currCode") ;
String notify_url = request.getParameter("notify_url")==null?"":request.getParameter("notify_url") ;
String result = "";

String out_trade_no="";

double orderAmt = Double.parseDouble(amount)*100;
System.out.println("orderAmt:"+orderAmt);
//-----------------------------
//設定支付引數
//-----------------------------
String sign = "";
String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String trade_type = "JSAPI";
String spbill_create_ip = request.getRemoteAddr();
String nonce_str = "";
String notify_url = notify_url;
String body = out_trade_no;
String appid = wechat_appid;
String sub_mch_id = "用wechat_appid查資料庫讀取出來比較好";
String mch_id = "用wechat_appid查資料庫讀取出來比較好";
System.out.println("wechat_appid:"+wechat_appid+",appid:"+appid);
String currTime = Method.getCurrTime();
String time_start = currTime.substring(8, currTime.length());
String strRandom = Method.buildRandom(4) + "";
nonce_str = time_start + strRandom; // Generating random number
String total_fee = String.valueOf((int)orderAmt);
String fee_type = CurrCode.getName(request.getParameter("currCode"));
String key = "用wechat_appid查資料庫讀取出來比較好  微信提供的key";
if("RMB".equals(fee_type))
    fee_type = "CNY";

System.out.println("datas:"+"\n"+"key:"+key+"\n"+"appid:"+appid+"\n"+"mch_id:"+mch_id+"\n"+"sub_mch_id:"+sub_mch_id+"\n"+"out_trade_no:"+out_trade_no+"\n"+"fee_type:"+fee_type+"\n"+"total_fee:"+total_fee+"\n"+"body:"+body+"\n"+"spbill_create_ip:"+spbill_create_ip+"\n"+"trade_type:"+trade_type+"\n"+"notify_url:"+notify_url+"\n"+"nonce_str:"+nonce_str);

Map<Object, String> map = new TreeMap<Object, String>();
map.put("appid", appid);//服務商appid
map.put("sub_mch_id", sub_mch_id);//商戶號
map.put("mch_id", mch_id);//服務商號
map.put("nonce_str", nonce_str);//隨機字串
map.put("body", body);//商品描述,用orderId
map.put("out_trade_no", out_trade_no);//商家訂單號
map.put("fee_type", fee_type);//幣種
map.put("total_fee", total_fee);//商品金額,以分為單位
map.put("spbill_create_ip", spbill_create_ip);//使用者公網ip
map.put("notify_url", notify_url);//微信反饋的接收url
map.put("trade_type", trade_type);//公眾號支付用JSAPI
map.put("openid", openid);
sign = Method.createSign("UTF-8", map,key);
map.put("sign", sign);

String requestXML = Method.getRequestXml(map);

Map map1 = null;

try{
    String resXml = ServerPost.postByExternalServerPost(requestXML,UFDODER_URL,"UTF-8");
    resXml = resXml.substring(1);
    System.out.println("resXml:"+resXml);
    map1 = XMLUtil.doXMLParse(resXml);
}catch (ConnectException ce){
    System.out.println("連線超時:"+ce.getMessage());
}catch (Exception e){
    System.out.println("https請求異常:"+e.getMessage());
}

if(map1==null){
    System.out.println("map null");
    result = "appId:"+appid+",timeStamp: ,nonceStr: ,package:prepay_id= ,signType: ,paySign: ";
}else{
    if(!map1.containsKey("prepay_id")||!map1.containsKey("appid")||map1.get("prepay_id")==""){
        System.out.println("prepayid error or appid error");
        result = "appId:"+appid+",timeStamp: ,nonceStr: ,package:prepay_id= ,signType: ,paySign: ";
    }else{
        String prepay_timeStamp = Method.getCurrTime();
        String prepay_time = currTime.substring(8, currTime.length());
        String prepay_strRandom = Method.buildRandom(4) + "";
        String prepay_nonce_str = nonce_str;
        String prepay_id = map1.get("prepay_id").toString();
        String prepay_SignType = "MD5";
        Map<Object, String> prepay_map = new TreeMap<Object, String>();
        prepay_map.put("appId", appid);
        prepay_map.put("timeStamp", prepay_timeStamp);
        prepay_map.put("nonceStr", prepay_nonce_str);
        prepay_map.put("package", ("prepay_id="+prepay_id));
        prepay_map.put("signType", prepay_SignType);
        String paySign = Method.createSign("UTF-8", prepay_map,key);

        result = "\"appId\":\""+appid+"\",\"timeStamp\":\""+prepay_timeStamp+"\",\"nonceStr\":\""+prepay_nonce_str+"\",\"package\":\"prepay_id="+prepay_id+"\",\"signType\":\""+prepay_SignType+"\",\"paySign\":\""+paySign+"\"";
    }
    System.out.println("result:"+result);
}
%>
<%if(!("appId:"+appid+",timeStamp: ,nonceStr: ,package:prepay_id= ,signType: ,paySign: ").equals(result)){ %>
<!-- ********************************* payment page ***************************************** -->
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>
            <fmt:message key="pg.eng.payment.payWECHATONLResult.text.title"/>
        </title>
        <link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
        <meta name="viewport" content="width=device-width">
        </script>
        <script src="../../js/jquery-1.4.2.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function(){
              callpay();
            });
        </script>

        <script>
          function close_cancel() {
              //根據appid,mch_id,out_trade_no,sub_mch_id呼叫關閉訂單介面 關閉訂單
          }
        </script> 

        <script type="text/javascript">
            //呼叫微信JS api 支付
            function jsApiCall()
            {
                WeixinJSBridge.invoke(
                    'getBrandWCPayRequest',{
                    <%=result%>
                    },
                    function(res){
                        WeixinJSBridge.log(res.err_msg);
                        if(res.err_msg == "get_brand_wcpay_request:cancel"){  
                            close_cancel();
                        }else if(res.err_msg == "get_brand_wcpay_request:fail"){
                            close_cancel();
                        }

                    }
                );
            }

            function callpay()
            {
                if (typeof WeixinJSBridge == "undefined"){
                    if( document.addEventListener ){
                        document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
                    }else if (document.attachEvent){
                        document.attachEvent('WeixinJSBridgeReady', jsApiCall); 
                        document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
                    }
                }else{
                    jsApiCall();
                }
            }
        </script>

        <script type="text/javascript">
            var start = setInterval(function(){order_status()},1000);
            function order_status() {
                //查詢自己資料庫 根據狀態對page_SorF賦值 然後提交表單
                /*
                $.ajax({
                    type: "POST",
                    url: "../../WECHATONLINEorderStatusCheck",
                    dataType: "text",
                    cache: false,
                    data: {
                        out_trade_no : '<%=out_trade_no%>'
                    }, 
                    success: function(data) {
                        if(data!=null){
                        var result = eval("(" + data + ")");
                        var s_result = result.status_result;
                            if("Accepted"==s_result){
                                clearInterval(start);
                                $("#page_SorF").val("successpage");
                                $("#PayWeChatForm").submit();
                            }else if("Rejected"==s_result){
                                clearInterval(start);
                                $("#page_SorF").val("failpage");
                                $("#PayWeChatForm").submit();
                            }else{}
                        }else{
                        }
                    }
                });
                */

            }
        </script>


    </head>
    <body>
        <div id="wrap">
            <form name="PayWeChatForm" id="PayWeChatForm" method="post" action="jsapiResult.jsp">

                                貨幣種類:<%=currCode %>
                                金額:   <%=amount %>

                <input type="hidden" id="amount" name="amount" value="<%=amount%>" >
                <input type="hidden" id="currCode" name="currCode" value="<%=request.getParameter("currCode")%>" >
                <input type="hidden" id="page_SorF" name="page_SorF" value="" >

            </form>
        </div>
    </body>

</html>
<!-- ********************************* payment page ***************************************** -->
<%}else{
    %>
    <html>
    <head></head>
    <body>
    <div>Message error</div>
    </body>
    </html>
    <%
  }%>

四、寫回調(微信會通過回撥把交易結果返回到Notify_url所在的地方)
Notify.jsp

<%@ page language="java" import="java.util.*,com.demo.*,com.demo.utils.*" pageEncoding="UTF-8"%>
<%out.println("success");%>
<%
//for getting wechat respon--------start
    String appid = "";
    String mch_id = "";
    String sub_mch_id = "";
    String trade_type = "";
    String bank_type = ""; 
    String total_fee = "";
    String fee_type = "";
    String cash_fee = "";
    String cash_fee_type = ""; 
    String transaction_id = "";
    String out_trade_no = "";
    String bankTxTime = "";
    String rate = "";
    String return_code = ""; 
    String result_code = "";

    //讀取引數  
    InputStream inputStream ;  
    StringBuffer sb = new StringBuffer();  
    inputStream = request.getInputStream();  
    String s ;  
    BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));  
    while ((s = in.readLine()) != null){  
        sb.append(s);  
    }  
    in.close();  
    inputStream.close();  

    //解析xml成map  
    Map<String, String> m = new HashMap<String, String>();  
    m = XMLUtil.doXMLParse(sb.toString());  

    //過濾空 設定 TreeMap  
    Map<Object,String> packageParams = new TreeMap<Object,String>();    
    Iterator it = m.keySet().iterator();  
    while (it.hasNext()) {  
        String parameter = (String) it.next();  
        String parameterValue = m.get(parameter);  

        String v = "";  
        if(null != parameterValue) {  
            v = parameterValue.trim();  
        }  
        packageParams.put(parameter, v);  
    }  

    //------------------------------  
  //處理業務開始  
  //------------------------------  
  String resXml = "";  
  if("SUCCESS".equals((String)packageParams.get("result_code"))){  
    // 這裡是支付成功  
      appid = (String)packageParams.get("appid"); 
      mch_id = (String)packageParams.get("mch_id"); 
      sub_mch_id = (String)packageParams.get("sub_mch_id"); 
      trade_type = (String)packageParams.get("trade_type"); 
      bank_type = (String)packageParams.get("bank_type"); 
      total_fee = (String)packageParams.get("total_fee"); 
      fee_type = (String)packageParams.get("fee_type"); 
      cash_fee = (String)packageParams.get("cash_fee"); 
      cash_fee_type = (String)packageParams.get("cash_fee_type"); 
      transaction_id = (String)packageParams.get("transaction_id"); 
      out_trade_no = (String)packageParams.get("out_trade_no");
      bankTxTime = (String)packageParams.get("time_end");
      rate = (String)packageParams.get("rate"); 
      return_code = (String)packageParams.get("return_code"); 
      result_code = (String)packageParams.get("result_code");
      System.out.println("Notify:"+"\n"+"appid:"+appid+"\n"+"mch_id:"+mch_id+"\n"+"sub_mch_id:"+sub_mch_id+"\n"+"trade_type:"+trade_type+"\n"+"bank_type:"+bank_type+"\n"+"total_fee:"+total_fee+"\n"+"fee_type:"+fee_type+"\n"+"cash_fee:"+cash_fee+"\n"+"cash_fee_type:"+cash_fee_type+"\n"+"transaction_id:"+transaction_id+"\n"+"out_trade_no:"+out_trade_no+"\n"+"rate:"+rate+"\n"+"return_code:"+return_code+"\n"+"result_code:"+result_code); 
      System.out.println("pay success");  
      //通知微信.非同步確認成功.必寫.不然會一直通知後臺.八次之後就認為交易失敗了.  
      resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"  
                  + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";  
   } else {  
      System.out.println("支付失敗,錯誤資訊:" + packageParams.get("err_code"));
      resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"  
                  + "<return_msg><![CDATA[報文            ></return_msg>" + "</xml> ";  
   }  
   //------------------------------  
   //處理業務完畢  
   //------------------------------  
   BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());  
   bos.write(resXml.getBytes());  
   bos.flush();  
   bos.close();  

//for getting wechat respon--------end

    boolean signValidate = false;

            // 賬號資訊  
            String key = "set wechat ket here"; // key  

            //判斷簽名是否正確  
            if(Method.isTenpaySign("UTF-8", packageParams,key)) {  
                signValidate = true;
                System.out.println("NOTIFY : " + "tenpay sign success");
             } else{  
                 signValidate = false;
                 System.out.println("NOTIFY : " + "tenpay sign fail");
             } 

            if ( (signValidate && return_code.equals("SUCCESS")) ) {
                if (signValidate && return_code.equals("SUCCESS") && result_code.equals("SUCCESS")) {

                    //對資料庫做交易成功的處理邏輯

                } else {

                    //對資料庫做交易失敗的處理邏輯
                }

            }

            out.clear();
            out = pageContext.pushBody();
%>

五、寫結果頁面
jsapiResult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%
request.setCharacterEncoding("UTF-8");
String page_SorF = request.getParameter("page_SorF")==null?"failpage":request.getParameter("page_SorF");  //successpage or failpage
String amount = request.getParameter("amount")==null?"":request.getParameter("amount");
String currCode = request.getParameter("currCode")==null?"":request.getParameter("currCode");
%>
<%
if("successpage".equals(page_SorF)){
%>
<!--************************success page start********************************-->
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>
            Result page
        </title>
        <link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
        <meta name="viewport" content="width=device-width">
        </script>
    </head>
    <body>
        <div>
            <form name="form1" method="post" action="">
            貨幣種類:<%=currCode %>
            金額:   <%=amount %>
            </form>
        </div>
    </body>

</html>
<!--*************************success page end*********************************-->
<%
}else if("failpage".equals(page_SorF)){
%>
<!--************************fail page start********************************-->
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>
        fail
    </title>
    <link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
    <meta name="viewport" content="width=device-width">
    </head>

  <body>
      <form name="failForm" method="post" action="payForm.jsp">
       <div>fail</div>
      </form>

  </body>

</html>
<!--*************************fail page end*********************************-->
<%
}else{
    %>
    <!--************************fail start********************************-->
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>
            failpage value null
        </title>
        <link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
        <meta name="viewport" content="width=device-width">
        </head>

      <body>
          <form name="failForm" method="post" action="payForm.jsp">
           <div>failpage value null</div>
          </form>

      </body>

    </html>
    <!--*************************fail end*********************************-->
    <%

}
%>

六、寫如果不是微信瀏覽器的時候的提示頁面
remind.jsp

<html>
 <head>
  <title>Wrong Browser</title>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0" />
  <link rel="stylesheet" type="text/css" href="https://res.wx.qq.com/open/libs/weui/0.4.1/weui.css" />
 </head> 
 <body>
  <div class="weui_msg">
   <div class="weui_icon_area">
    <i class="weui_icon_info weui_icon_msg"></i>
   </div>
   <div class="weui_text_area">
    <h4 class="weui_msg_title">Please open with WeChat</h4>
   </div>
  </div> 
 </body>
</html>

總結:程式碼寫出來很快(com.demo.utils.*裡面那些Method.xxx()、XMLUtils.xxx之類的方法,微信demo上有,我之前寫的微信支付貼也有,只是一些生成隨機字元、解析xml之類的方法,自己直接寫也可以,很簡單的),不過需要注意細節(redirect_url需要先urlencode、判斷是否微信瀏覽器、微信賬號的配置、code/openId/prepay_id的獲取和使用),這些細節就是坑,做少了做錯了就走不通。