微信统一支付详解,坑太多,不得不写

7 篇文章 0 订阅

最近开发app支付,支付宝按照开发文档很快搞定,本想微信支付开发也一样的容易,结果我错了,一路踩坑不断,到最后终于完成,耗了不少时间和精力,所以想写一篇关于微信统一支付的开发过程,希望大家能少走弯路

本文章适用于微信公众号支付开发,用的方式是统一支付接口,请对号入座,因为有好几种支付接口

只能是在手机App内的微信公众号内微信浏览器使用;PC电脑、手机浏览器(但不在微信公众号内的浏览器)是无法适用

另外说下

H5的支付方式(适用于任意浏览器),无法申请,不够资格;

Wap的支付方式(适用于任意浏览器),无法申请,不够资格;

:(


准备工作

一、微信公众号这部分的配置

申请公众号   https://mp.weixin.qq.com

  1.  申请好后,做下图配置,看1,2点


申请支付商户  

  1.  申请好后,得到下图,看1点,记下商户号

点击上图中的“开发配置”,做以下配置,如图

 授权目录url的注意地方

比如:您准备要写的支付调用页面是 http://xxx.xxx.xxx/abc/pay/weixin_pay.jsp 这个

则 授权目录就得写成

 http://xxx.xxx.xxx/abc/pay/


至此,微信公众号就配置好了


二、微信支付的配置

登陆微信支付平台

https://pay.weixin.qq.com

在您申请成功微信支付审核通过后,会有一封邮件发给你,里边有帐号和密码什么的。

登陆进去后,点击顶部的“账户中心”,再点左边菜单的“api安全”,按下图配置


这样,微信支付这部分也大概设置好了。

三、到这里,我们总共记下了这些信息,如下图



四、以上准备好后,就可以准备写代码了

顺序一般是

pay_type.jsp (A支付类型选择) => pay_order_info.jsp(B商品订单信息) => wexinpay_oauth2.jsp(C统一支付)

a)支付类型选择,就是指选择支付宝、微信、银联等支付方式;

b)A页面提交后(选择微信支付),到B页面显示商品订单的详情,包括订单编号(order_no)、数量及金额等,这里开始就得写微信支付相关代码了,就从这里说起;

B页面,提交后,就到C统一支付页面。


以下是详细说明

B页面的订单信息,有表单提交按钮

<button οnclick="onpay();">确认提交订单</button>

这里要先取得用户信息,用 oauth2授权

注意:url的位置在前面的授权目录内

appid=你的微信公众号appid

订单提交代码如下

<script>
function onpay(){
var return_url = encodeURIComponent('http://xxx.xxx.xxx/abc/pay/wexinpay_oauth2.jsp');
var oauth2_url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=微信公众号appid&redirect_uri='+return_url+'&response_type=code&scope=snsapi_base&state='+state+'#wechat_redirect';

						document.getElementById("jtform").target = '_blank';
						document.getElementById("jtform").action = oauth2_url;


}

</script>

这样,在点击“确认提交订单”按钮后,表单的订单内容信息,就会post到(统一支付页面)

http://xxx.xxx.xxx/abc/pay/wexinpay_oauth2.jsp

重要

下面,我们再看这个C统一支付页面的写法

在徽信公众号浏览器下,可以直接带回code参数,而不需要微信用户点击授权确认;

wexinpay_oauth2.jsp 内关键代码如下


<html>
<head>
    <meta charset="utf-8" />
    <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
    <script src="jquery/jquery.min.js"></script>  <!--引入你的jquery-->
<script>
    //执行支付
    function executePay(){
                     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();  
             }  
    }

    //执行支付 

  function onBridgeReady(){   
      var result = document.getElementById("payString").value; 
      var payJson = eval('('+result+')'); //就是统一支付最终返回的json字符串     
      if(result!=''&&payJson['paySign']!=undefined&&payJson['paySign']!=null&&payJson['paySign']!=''){ 
          WeixinJSBridge.invoke(    
            'getBrandWCPayRequest',{
            "appId":payJson['appId'], 
            "timeStamp":payJson['timeStamp'],//时间戳,自1970年以来的秒数
            "nonceStr":payJson['nonceStr'], //随机串
            "package":payJson['package'],
            "signType":"MD5",  //微信签名方式:
           "paySign":payJson['paySign'] //微信签名
            },function(res){
             if(res.err_msg == "get_brand_wcpay_request:ok" ) {
                 alert('支付成功!');
/*在这里其实是不保险的,有可能在支付时,用户提前关闭或断线什么的,不能保证真正的支付结果,
应该在统一支付的回调url中处理,支付后的状态与订单状态同步,
请看第五点 ,统一支付的回调处理//回调的url本例设置为 
String notify_url = "http://xxx.xxx.xxx/abc/pay/wexin_order_pay.jsp"; //通知回调地址,不能有参数*/
                        var curr_url = '商户展示界面'; 
              window.location.href = curr_url; 
            }else{   
                      alert('支付失败!');
            var curr_url = '商户失败界面';
           window.location.href = curr_url;

     }    /* 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 */
    });      
  }else{
   alert('支付校验未能通过!');  
    var curr_url = '商户失败界面'; 
    window.location.href = curr_url;           
  }
 }
</script>


</head>

<body οnlοad="executePay();"> 

//页面加载完成后,调用executePay()执行支付...略

<%

String code = String.valueOf(request.getParameter("code")).replaceAll("null", ""); //取得传过来的code
String order_no = String.valueOf(request.getParameter("order_no")).replaceAll("null", ""); //取得传过来的订单编号

String userIp = '取得用户端的访问ip'; //代码略....
WeiXinPayDao  wxPayDao = new WeiXinPayDao();   //做了一个微信支付业务类


String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appid+"&secret="+secret+"&code="+code+"&grant_type=authorization_code";
//用公众号的appid,key,刚接收的code组成新的url,来获取微信用户的openid
String info = wxPayDao.httpRequest(url);

  if(info!=null&&!info.equals("")){
     if(info.indexOf("errcode")==-1){
            JSONObject joInfo = new JSONObject(info);
            access_token = joInfo.getString("access_token"); 
            refresh_token = joInfo.getString("refresh_token");
            openid = joInfo.getString("openid");
            unionid = joInfo.getString("unionid");}}

/*
如果以上没有什么问题的话,就可以取得当前微信用户的openid;
接下来,支付准备工作就差不多完成,终于到了调用统一支付接口这里了,统一支付接口调用就是为了取得 prepay_id 的值,有了这个值,就能发起支付
*/

/**
根据订单编号order_no,查询订单信息放到 Map orderMap = new HashMap();

       orderMap  就是存放订单信息的,代码略....

 */

/**
接下来, 调用统一支付接口....

*/ 

String payJson = wxPayDao.pay(openid,orderMap,userIp); //参数openid,orderMap(订单信息),userIp(微信用户端的真实ip),得到一个json字符串

//把最终带 prepay_id 参数的字符串的值,放到界面的一个隐藏域内,这个值给executePay(),页面初始化时,发起支付
if(!payJson .equals("")){                
  payJson = payJson.replaceAll("\"", "'");
%>  
  <input id="payString" name="payString" value="<%=payJson%>" type="hidden">
<% 
}
%>
</body>
</html>

以下是代码补充

补方法:
WeiXinPayDao  

pay(openid,orderMap,userIp),是一个方法,代码如下

  /**
     * 统一下单接口
     * openid 微信用户id
     * orderMap 订单信息map
     * user_ip  微信用户ip 
    * */
public String pay(String openid,Map orderMap,String user_ip){

String result = getResult("0","pass",null);    //本方法最终返回的字符串
String order_no = String.valueOf(orderMap.get("order_no")); //订单号
String device_info = "WEB";
String nonce_str = UUID.randomUUID().toString().replaceAll("-", ""); //随机字串,32位
String body = String.valueOf(orderMap.get("order_name")).replaceAll("null", ""); //商品描述,我这里用订单名称
String total_fee = "2" ;//总金额 ,单位是分
String fee_type = "CNY"; //币种
String notify_url = "http://xxx.xxx.xxx/abc/pay/wexin_order_pay.jsp"; //通知回调地址,不能有参数
String trade_type = "JSAPI"; //交易类型 公众号=JSAPI
String limit_pay = "no_credit"; //上传此参数no_credit--可限制用户不能使用信用卡支付
//然后把以上参数放到一个map中
Map paramsMap = new HashMap();
paramsMap.put("appid", appid); //微信公众号
paramsMap.put("mch_id", mch_id); //商户号,前面就是你记下来的
paramsMap.put("device_info", device_info);
paramsMap.put("nonce_str", nonce_str);
paramsMap.put("body", body);
paramsMap.put("total_fee", total_fee);
paramsMap.put("spbill_create_ip", user_ip);
paramsMap.put("notify_url", notify_url);
paramsMap.put("trade_type", trade_type);
paramsMap.put("limit_pay", limit_pay);
paramsMap.put("attach", order_no);
paramsMap.put("fee_type", fee_type);
paramsMap.put("openid", openid);
paramsMap.put("out_trade_no",order_no);//然后用以上参数排个序,连成一个参数字符串
String[] arr = new String[]{"appid","mch_id","device_info","nonce_str","total_fee",
"attach","fee_type","spbill_create_ip","notify_url","trade_type",
"limit_pay","body","openid","out_trade_no"};
Arrays.sort(arr); 
String stringA = "";
int arr_len = arr.length;
for(int i=0;i<arr_len;i++){
stringA = stringA + "&"+arr[i]+"=" + paramsMap.get(arr[i]);
}
if(!stringA.equals("")){
stringA = stringA.substring(1);
}
String stringSignTemp = stringA+"&key=支付的key,注意不是公众号的key了";
String sign = Md5Encode.md5Encoding(stringSignTemp).toUpperCase();
//做一次md5加密,结果转成大写//然后,用sign的值,再拼一次xml格式的字符串
StringBuffer payXmlBuff = new StringBuffer();
payXmlBuff.append("<xml>")
.append("<appid>").append(appid).append("</appid>")
.append("<attach>").append(order_no).append("</attach>")
.append("<body><![CDATA[").append(body).append("]]></body>")
.append("<device_info>").append(device_info).append("</device_info>")
.append("<fee_type>").append(fee_type).append("</fee_type>")
.append("<limit_pay>").append(limit_pay).append("</limit_pay>")
.append("<mch_id>").append(mch_id).append("</mch_id>")
.append("<nonce_str>").append(nonce_str).append("</nonce_str>")
.append("<notify_url>").append(notify_url).append("</notify_url>")
.append("<openid>").append(openid).append("</openid>")
.append("<out_trade_no>").append(order_no).append("</out_trade_no>")
.append("<spbill_create_ip>").append(user_ip).append("</spbill_create_ip>")
.append("<total_fee>").append(total_fee).append("</total_fee>")
.append("<trade_type>").append(trade_type).append("</trade_type>")
.append("<sign>").append(sign).append("</sign>")
.append("</xml>");

/**然后,把带sign的xml拼成的字符串,发送到统一支付接口*/ 
String payUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //统一支付接口的url
//然后,把上面拼接好的xml字符串,发到统一支付接口

        HttpClient httpClient = new DefaultHttpClient();
         HttpPost post = new  HttpPost(payUrl);
         post.setEntity(new StringEntity(payXmlBuff.toString(),"UTF-8")); //注意utf-8
         try {
            HttpResponse execute = httpClient.execute(post);
            HttpEntity entity = execute.getEntity();  
            String responseContent = EntityUtils.toString(entity,"utf-8");  //注意utf-8 ,responseContent 的结果也是一个xml字符串
            //System.out.println("PayController index获取到的总数据:"+responseContent);    
/*判断是否成功,返回的字符串结果中,标志有SUCCESS及prepay_id参数,为成功*/
 if(responseContent.indexOf("SUCCESS")!=-1&&responseContent.indexOf("prepay_id")!=-1){
                try {
//对返回的xml字符串做解析,这里用的是dom4j包
                    Document document = DocumentHelper.parseText(responseContent);
                    Element root = document.getRootElement();
                    result = String.valueOf(root.elementText("prepay_id")).replaceAll("null", "");
                    //System.out.println("prepay_id:="+result);
                    String payTimeStamp =  Long.toString(System.currentTimeMillis()/1000); //去掉三位,精确到秒
                    String payNonceStr = UUID.randomUUID().toString().replaceAll("-", ""); //随机串
                    
//跟前面一样,把参数放到map 
                    String payarr[] = new String[]{"appId","timeStamp","nonceStr","package","signType"};
                    Map payParamMap = new HashMap();
                    payParamMap.put("appId", appid);
                    payParamMap.put("timeStamp",payTimeStamp);
                    payParamMap.put("nonceStr", payNonceStr);
                    payParamMap.put("package", "prepay_id="+result);
                    payParamMap.put("signType", "MD5");

                    Arrays.sort(payarr); //排序
                    String payA = "";
                    int pay_len = payarr.length;
                    for(int i=0;i<pay_len;i++){
                        payA = payA + "&"+payarr[i]+"=" + payParamMap.get(payarr[i]);
                    }
                    if(!payA.equals("")){
                        payA = payA.substring(1);
                    }
                    String paySignTemp = payA+"&key=支付的key,注意不是公众号的key";
                    //System.out.println("paySignTemp:"+paySignTemp);
                    String paySign = Md5Encode.md5Encoding(paySignTemp).toUpperCase();
                    //System.out.println("paysign:"+paySign);
                    payParamMap.put("paySign", paySign);
                    
                    JSONObject payJson = new JSONObject(payParamMap);
                    result = payJson.toString(); 
//做成一个json字符串,返回给 http://xxx.xxx.xxx/abc/pay/wexinpay_oauth2.jsp 界面的隐藏域内
                    
                } catch (DocumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();                    
result = getResult("999","支付出错!",null);
                } 
                
            
            }
            
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            result = getResult("999","支付出错!",null);
            

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            result = getResult("999","支付出错!",null);
        }  
         
}

补方法:
WeiXinPayDao  
getResult(code,msg,infoMap)是一个方法,代码如下

  /**
     * 生成返回信息
     * */
    private String getResult(String code,String msg,Map infoMap){
        JSONObject result = new JSONObject();
        result.put("code",code);
        result.put("msg",msg);
        if(infoMap!=null&&!infoMap.isEmpty()){
            result.put("info", infoMap);
        }
        return result.toString();
    }

补方法:
WeiXinPayDao  
httpRequest(url),是一个方法,代码如下

 /** 
     * 发起http请求获取返回结果 
     * @param req_url 请求地址 
     * @return 
     */ 
    private String httpRequest(String req_url) {
        StringBuffer buffer = new StringBuffer();  
        try {  
            URL url = new URL(req_url);  
            HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();  
   
            httpUrlConn.setDoOutput(false);  
            httpUrlConn.setDoInput(true);  
            httpUrlConn.setUseCaches(false);  
   
            httpUrlConn.setRequestMethod("GET");  
            httpUrlConn.connect();  
            // 将返回的输入流转换成字符串  
            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 (Exception e) {  
            System.out.println(e.getStackTrace());  
        }  
        return buffer.toString();  
    }  

五、统一支付回调处理

http://xxx.xxx.xxx/abc/pay/wexin_order_pay.jsp
回调处理 ,主要就是同步徽信支付状态和商户订单的支付状态,成功或失败

关键代码如下

<html>
<head>
<body>略...
<%

String return_xml = "";
        //获取徽信那边post过来的数据流
   	String acceptjson=null;
                try {
                     BufferedReader br = new BufferedReader(new InputStreamReader( (ServletInputStream) request.getInputStream(), "utf-8"));  
                               StringBuffer sb = new StringBuffer("");  
                               String temp;  
                               while ((temp = br.readLine()) != null) {  
                                     sb.append(temp);  
                               }  
                                 br.close();  
                               acceptjson = sb.toString();  
                               //  System.out.print("acceptjson="+acceptjson);
                             } catch (Exception e) {  
                                 e.printStackTrace();    
                             } 
        //判断处理 ,根据你自己实际的业务来写 ,acceptjson 实际也是一个xml字符串,用dom4j解析
     if(acceptjson!=null&&!acceptjson.equals("")){    
        
        String source = "eva";
        String appid = "";
        String attach = "";
        String mch_id = "";
        String out_trade_no = "";
        String result_code = "";
        String return_code = "";
        String time_end = "";
        String transaction_id = "";
        try {
            Document document = DocumentHelper.parseText(acceptjson);
            Element elRoot = document.getRootElement();
            appid = String.valueOf(elRoot.elementText("appid")).replaceAll("null", "");
            attach = String.valueOf(elRoot.elementText("attach")).replaceAll("null", "");
            mch_id = String.valueOf(elRoot.elementText("mch_id")).replaceAll("null", "");
            out_trade_no = String.valueOf(elRoot.elementText("out_trade_no")).replaceAll("null", "");
            result_code = String.valueOf(elRoot.elementText("result_code")).replaceAll("null", "");
            return_code = String.valueOf(elRoot.elementText("return_code")).replaceAll("null", "");
            time_end = String.valueOf(elRoot.elementText("time_end")).replaceAll("null", "");
            transaction_id = String.valueOf(elRoot.elementText("transaction_id")).replaceAll("null", "");
            //合法性校验
            if(appid.equals("您的公众号")&&mch_id.equals("您的商户号")){
                          //以下订单的处理只做参考
                if(!out_trade_no.equals("")){
                                 //订单同步处理
                    JdbcHelp jdbc = JdbcHelpUtil.getInstance();
                    String querySQL = "select 1 from hq_order_head where payment_status=8 and order_no="+out_trade_no;
 //是否已经支付过了
                    Form orForm = new Form();
                    orForm.setValue(JdbcConstant.SQL, querySQL);
                    orForm.setValue(JdbcConstant.SOURCE, source);
                    Map qMap = jdbc.queryMap(orForm);
                    if(qMap.isEmpty()){
                        if(result_code.equals("SUCCESS")){
                            time_end = UtilTools.getInstance().formatDate("yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss", time_end);
                            String upSQL = "update hq_order_head set payment_status=8,status=8,payment_date=?,payment_no=? where order_no="+out_trade_no;
                            orForm.setValue(JdbcConstant.PARAMES_FIELDS, "payment_date,payment_no");
                            orForm.setValue("payment_date", time_end);
                            orForm.setValue("payment_no", transaction_id);
                            orForm.setValue(JdbcConstant.SQL, upSQL);
                            jdbc.update(orForm);
                            jdbc.commit(source);
                        }
                    }
                    
                    return_xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[]]></return_msg></xml>";
                }
            }else{
                return_xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
            }
            
           
        } catch (DocumentException e1) {
           
            return_xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
        } 
            
    }else{
        return_xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
    }
      //返回处理结果给徽信那边,值是xml字符串, SUCCESS或FAIL
    response.getWriter().print(return_xml);

%>
</body>
</html>


到此,整个微信公众号统一支付介绍完了

以上代码是关键代码,非完整,但不影响阅读和参考。


希望对需要的人有用:)



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值