最近开发app支付,支付宝按照开发文档很快搞定,本想微信支付开发也一样的容易,结果我错了,一路踩坑不断,到最后终于完成,耗了不少时间和精力,所以想写一篇关于微信统一支付的开发过程,希望大家能少走弯路
本文章适用于微信公众号支付开发,用的方式是统一支付接口,请对号入座,因为有好几种支付接口
只能是在手机App内的微信公众号内微信浏览器使用;PC电脑、手机浏览器(但不在微信公众号内的浏览器)是无法适用
另外说下
H5的支付方式(适用于任意浏览器),无法申请,不够资格;
Wap的支付方式(适用于任意浏览器),无法申请,不够资格;
:(
准备工作
一、微信公众号这部分的配置
申请公众号 https://mp.weixin.qq.com
申请好后,做下图配置,看1,2点申请支付商户
申请好后,得到下图,看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> 到此,整个微信公众号统一支付介绍完了 以上代码是关键代码,非完整,但不影响阅读和参考。 希望对需要的人有用:)
