对开发测试成功后的源码以及在开发中遇到的问题做一下简要说明
开发之前的准备工作:
参数:开发之前你要知道请求每个接口地址的参数所代表的含义 //下面四个是必须知道的参数
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转换为大写
***** 以上就是我对微信公众号支付开发的总结,希望能对您有所帮助,再次感谢您的阅读 *****