最近一段时间换了工作,虽说时间不长但也算收获颇丰,不仅学到了新的知识而且眼界也比之前开阔了。这次就分享下这段时间做微信开发的心得和体会。
一 java微信开发应具备的前提条件
1 掌握xml解析工具Dom4j、Jdom中的任意一种
微信所有的消息处理都是xml,因此xml的解析就显得尤为重要,这集中体现在文本消息、图文消息这两个部分
2 掌握JSON开发工具类如json-lib
json数据的处理在微信开发集中体现在自定义菜单接口、获取Access_Token、Oauth2.0网页授权等常用接口,此外第三方接口也会使用到如百度翻译、百度词典等。
3 掌握xstream
xstream的用途集中体现在java对象转xml字符串这个方面,使用xstream主要是为了最大程度地发挥java面向对象的特点。
4 熟悉MD5和SHA-1加密算法
加密算法 主要用于微信验证签名和生成签名(微信支付)两个部分
5 掌握HTTPConnection和HTTPSConnecion
这个部分一帮的第二点配合使用以达到最佳效果
6 掌握常用数据库
7 能熟练使用linux操作系统
这7点是2每个java微信开发者都必须具备的,如果这几点都没办法掌握,微信定制开发你就高攀不起。
二 下面是几个常用工具类
1 微信消息处理工具类
package com.debug.weixin.util; import java.io.InputStream; import java.io.Writer; import java.util.*; import javax.servlet.http.HttpServletRequest; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.debug.weixin.message.resp.Article; import com.debug.weixin.message.resp.MusicMessage; import com.debug.weixin.message.resp.NewsMessage; import com.debug.weixin.message.resp.TextMessage; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; public class MessageUtil { /** * 返回消息类型:文本 */ public static final String PICTUREHOST = "http://xxxx"; /** * 返回消息类型:文本 */ public static final String RESP_MESSAGE_TYPE_TEXT = "text"; /** * 返回消息类型:音乐 */ public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; /** * 返回消息类型:图文 */ public static final String RESP_MESSAGE_TYPE_NEWS = "news"; /** * 请求消息类型:文本 */ public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /** * 请求消息类型:图片 */ public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /** * 请求消息类型:链接 */ public static final String REQ_MESSAGE_TYPE_LINK = "link"; /** * 请求消息类型:地理位置 */ public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /** * 请求消息类型:音频 */ public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /** * 请求消息类型:推送 */ public static final String REQ_MESSAGE_TYPE_EVENT = "event"; /** * 事件类型:subscribe(订阅) */ public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /** * 事件类型:unsubscribe(取消订阅) */ public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /** * 事件类型:CLICK(自定义菜单点击事件) */ public static final String EVENT_TYPE_CLICK = "CLICK"; /** * 解析微信发来的请求(XML) * * @param request * @return * @throws Exception */ @SuppressWarnings("unchecked") public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); // 从request中取得输入流 InputStream inputStream = request.getInputStream(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) map.put(e.getName(), e.getText()); // 释放资源 inputStream.close(); inputStream = null; return map; } /** * 文本消息对象转换成xml * * @param textMessage 文本消息对象 * @return xml */ public static String textMessageToXml(TextMessage textMessage) { xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); } /** * 音乐消息对象转换成xml * * @param musicMessage 音乐消息对象 * @return xml */ public static String musicMessageToXml(MusicMessage musicMessage) { xstream.alias("xml", musicMessage.getClass()); return xstream.toXML(musicMessage); } /** * 图文消息对象转换成xml * * @param newsMessage 图文消息对象 * @return xml */ public static String newsMessageToXml(NewsMessage newsMessage) { xstream.alias("xml", newsMessage.getClass()); xstream.alias("item", new Article().getClass()); return xstream.toXML(newsMessage); } /** * 扩展xstream,使其支持CDATA块 * * @date 2013-05-19 */ private static XStream xstream = new XStream(new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 对所有xml节点的转换都增加CDATA标记 boolean cdata = true; @SuppressWarnings("unchecked") public void startNode(String name, Class clazz) { super.startNode(name, clazz); } protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { writer.write(text); } } }; } }); }
package com.debug.weixin.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; public class SignUtil { // 与接口配置信息中的Token要一致 private static String token = "xxxxxx"; /** * 验证签名 * * @param signature * @param timestamp * @param nonce * @return */ public static boolean checkSignature(String signature, String timestamp, String nonce) { String[] arr = new String[] { token, timestamp, nonce }; // 将token、timestamp、nonce三个参数进行字典序排序 Arrays.sort(arr); StringBuilder content = new StringBuilder(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); } MessageDigest md = null; String tmpStr = null; try { md = MessageDigest.getInstance("SHA-1"); // 将三个参数字符串拼接成一个字符串进行sha1加密 byte[] digest = md.digest(content.toString().getBytes()); tmpStr = byteToStr(digest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } content = null; // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信 return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false; } /** * 将字节数组转换为十六进制字符串 * * @param byteArray * @return */ private static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } /** * 将字节转换为十六进制字符串 * * @param mByte * @return */ private static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s; } }
3 xml解析工具类
package com.debug.weixin.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class XMLUtil { /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map doXMLParse(String strxml) throws JDOMException, IOException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if(null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = XMLUtil.getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * @param children * @return String */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } }
package com.debug.weixin.util; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import com.debug.weixin.pojo.AccessToken; import com.debug.weixin.pojo.Menu; import net.sf.json.JSONException; import net.sf.json.JSONObject; public class WeixinUtil { public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; // 菜单创建(POST) 限100(次/天) public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; // 菜单删除 public static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN"; /** * 发起https请求并获取结果 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; 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(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { ce.printStackTrace(); // log.error("Weixin server connection timed out."); } catch (Exception e) { //log.error("https request error:{}", e); e.printStackTrace(); } return jsonObject; } /** * 获取access_token * * @param appid 凭证 * @param appsecret 密钥 * @return */ public static AccessToken getAccessToken(String appid, String appsecret) { AccessToken accessToken = null; String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET", null); // 如果请求成功 if (null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in")); } catch (JSONException e) { accessToken = null; // 获取token失败 //log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); System.out.println("获取token失败"+jsonObject.getInt("errcode")+"," +jsonObject.getString("errmsg")); } } return accessToken; } /** * 创建菜单 * * @param menu 菜单实例 * @param accessToken 有效的access_token * @return 0表示成功,其他值表示失败 */ public static int createMenu(Menu menu, String accessToken) { int result = 0; // 拼装创建菜单的url String url = menu_create_url.replace("ACCESS_TOKEN", accessToken); // 将菜单对象转换成json字符串 String jsonMenu = JSONObject.fromObject(menu).toString(); // 调用接口创建菜单 JSONObject jsonObject = httpRequest(url, "POST", jsonMenu); if (null != jsonObject) { if (0 != jsonObject.getInt("errcode")) { result = jsonObject.getInt("errcode"); //log.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); System.out.println("创建菜单失败"+jsonObject.getInt("errcode")+","+ jsonObject.getString("errmsg")); } } return result; } public static void removeAllMenu(String accessToken){ // 拼装创建菜单的url String url = menu_delete_url.replace("ACCESS_TOKEN", accessToken); // 调用接口创建菜单 JSONObject jsonObject = httpRequest(url, "POST", null); if (null != jsonObject) { //if (0 != jsonObject.getInt("errcode")) { System.out.println(jsonObject.getInt("errcode")+","+ jsonObject.getString("errmsg")); //} } } }
5 MD5工具类(主要用于微信支付)
package com.debug.weixin.util; import java.security.MessageDigest; public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; }
package com.debug.weixin.util; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.SortedMap; public class PayCommonUtil { public static String CreateNoncestr(int length) { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < length; i++) { Random rd = new Random(); res += chars.indexOf(rd.nextInt(chars.length() - 1)); } return res; } public static String CreateNoncestr() { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < 16; i++) { Random rd = new Random(); res += chars.charAt(rd.nextInt(chars.length() - 1)); } return res; } /** * @author 李欣桦 * @date 2014-12-5下午2:29:34 * @Description:sign签名 * @param characterEncoding 编码格式 * @param parameters 请求参数 * @return */ public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序) Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); Object v = entry.getValue(); if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + ConfigUtil.API_KEY); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * @author 李欣桦 * @date 2014-12-5下午2:32:05 * @Description:将请求参数转换为xml格式的string * @param parameters 请求参数 * @return */ public static String getRequestXml(SortedMap<Object,Object> parameters){ StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) { sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">"); }else { sb.append("<"+k+">"+v+"</"+k+">"); } } sb.append("</xml>"); return sb.toString(); } /** * @author 李欣桦 * @date 2014-12-3上午10:17:43 * @Description:返回给微信的参数 * @param return_code 返回编码 * @param return_msg 返回信息 * @return */ public static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; } }
7 证书信任管理器(主要用于HttpsConnection)
关于这个部分我也专门写过一篇博客的,这里就直接上代码了
package com.debug.weixin.util; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; /** * 证书信任管理器(用于https请求) * @date 2013-08-08 */ public class MyX509TrustManager implements X509TrustManager{ public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }
这个部分主要是对csdn上“情本寂寞”这位大神(就上面的那位李欣桦)的博客做补充说明。
1 对Oauth2.0网页授权获取openid的补充
这个地方是支付环节最容易出错也是最困难的部分,我给出了几个方法以供参考
Oauth2.0网页授权第一步:
@RequestMapping("/wxIndex") public void wxIndex(HttpServletRequest request, HttpServletResponse response) throws Exception { String orderNo=request.getParameter("orderNo"); String re = URLEncoder.encode(ServerConfig.SERVERDOMAIN+"/waphulai/order/wxOAuth.do?orderNo="+orderNo, "UTF-8"); String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect"; url = url.replace("APPID", "xxxxxx"); url = url.replace("REDIRECT_URI", re); response.sendRedirect(url); }
@RequestMapping("/wxOAuth") public String wxOAuth(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); String code = request.getParameter("code"); String orderNo=request.getParameter("orderNo"); if (!"authdeny".equals("code")) { WeixinOauth2Token w = AdvancedUtil.getOauth2AccessToken("xxxxx", "xxxxxx",code); // 网页授权接口访问凭证 String accessToken = w.getAccessToken(); // 用户标识 String openId = w.getOpenId(); // 获取用户信息 /*SNSUserInfo snsUser = AdvancedUtil.getSNSUserInfo(accessToken, openId); request.setAttribute("snsUserInfo", snsUser);*/ Order orderInfo=orderService.getOrderByNo(orderNo); SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put("appid", ConfigUtil.APPID); parameters.put("mch_id", ConfigUtil.MCH_ID); parameters.put("device_info", "1000"); parameters.put("body", "洗车订单"); parameters.put("nonce_str", PayCommonUtil.CreateNoncestr()); parameters.put("out_trade_no", orderInfo.getOrder_no()); parameters.put("total_fee", String.valueOf(orderInfo.getOrder_money()*100)); parameters.put("spbill_create_ip", request.getRemoteAddr()); parameters.put("notify_url", ConfigUtil.NOTIFY_URL); parameters.put("trade_type", "JSAPI"); parameters.put("openid", openId); String sign = PayCommonUtil.createSign("UTF-8", parameters); //System.out.println("我 的签名是:"+sign); parameters.put("sign", sign); String requestXML = PayCommonUtil.getRequestXml(parameters); String result = CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL,"POST", requestXML); System.out.println("----------------------------------"); System.out.println(result); System.out.println("----------------------------------"); System.out.println(String.valueOf(orderInfo.getOrder_money()*100)+"----------------------------------"); request.setAttribute("orderNo", orderInfo.getOrder_no()); request.setAttribute("totalPrice", orderInfo.getOrder_money()); request.setAttribute("unifiedOrder",getH5PayStr(result,request)); } return "/weixin/wxPay.ftl"; }
package com.debug.weixin.util; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import com.debug.weixin.pojo.SNSUserInfo; import com.debug.weixin.pojo.WeixinOauth2Token; public class AdvancedUtil { public static WeixinOauth2Token getOauth2AccessToken(String appId,String appSecret,String code){ WeixinOauth2Token wat=null; //拼接请求地址 String requestUrl="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; requestUrl=requestUrl.replace("APPID", appId); requestUrl=requestUrl.replace("SECRET", appSecret); requestUrl=requestUrl.replace("CODE", code); //获取网页授权凭证 JSONObject jsonObject=WeixinUtil.httpRequest(requestUrl, "GET", null); if(jsonObject!=null){ try{ wat=new WeixinOauth2Token(); wat.setAccessToken(jsonObject.getString("access_token")); wat.setExpiresIn(jsonObject.getInt("expires_in")); wat.setRefreshToken(jsonObject.getString("refresh_token")); wat.setOpenId(jsonObject.getString("openid")); wat.setScope(jsonObject.getString("scope")); }catch(Exception e){ wat=null; int errorCode=jsonObject.getInt("errcode"); String errorMsg=jsonObject.getString("errmsg"); System.out.println("获取网页授权凭证失败:"+errorCode+","+errorMsg); } } return wat; } public static WeixinOauth2Token refreshOauth2AccessToken(String appId,String refreshToken){ WeixinOauth2Token wat=null; //拼接请求地址 String requestUrl="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN"; requestUrl=requestUrl.replace("APPID", appId); requestUrl=requestUrl.replace("REFRESH_TOKEN", refreshToken); //获取网页授权凭证 JSONObject jsonObject=WeixinUtil.httpRequest(requestUrl, "GET", null); if(jsonObject!=null){ try{ wat=new WeixinOauth2Token(); wat.setAccessToken(jsonObject.getString("access_token")); wat.setExpiresIn(jsonObject.getInt("expires_in")); wat.setRefreshToken(jsonObject.getString("refresh_token")); wat.setOpenId(jsonObject.getString("openid")); wat.setScope(jsonObject.getString("scope")); }catch(Exception e){ wat=null; int errorCode=jsonObject.getInt("errcode"); String errorMsg=jsonObject.getString("errmsg"); System.out.println("刷新网页授权凭证失败:"+errorCode+","+errorMsg); } } return wat; } public static SNSUserInfo getSNSUserInfo(String accessToken,String openId){ SNSUserInfo snsUserInfo=null; String requestUrl="https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; requestUrl=requestUrl.replace("ACCESS_TOKEN", accessToken); requestUrl=requestUrl.replace("OPENID", openId); //通过网页获取用户信息 JSONObject jsonObject=WeixinUtil.httpRequest(requestUrl, "GET", null); if(jsonObject!=null){ try{ snsUserInfo=new SNSUserInfo(); snsUserInfo.setOpenId(jsonObject.getString("openid")); snsUserInfo.setNickname(jsonObject.getString("nickname")); //性别(1男 2女 0未知) snsUserInfo.setSex(jsonObject.getInt("sex")); snsUserInfo.setCountry(jsonObject.getString("country")); snsUserInfo.setProvince(jsonObject.getString("province")); snsUserInfo.setCity(jsonObject.getString("city")); snsUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl")); snsUserInfo.setPrivilegeList(JSONArray.toList(jsonObject.getJSONArray("privilege"))); }catch(Exception e){ snsUserInfo=null; int errorCode=jsonObject.getInt("errcode"); String errorMsg=jsonObject.getString("errmsg"); System.out.println("获取用户信息失败:"+errorCode+","+errorMsg); } } return snsUserInfo; } }
public String getH5PayStr(String result,HttpServletRequest request) throws Exception{ Map<String, String> map = XMLUtil.doXMLParse(result); SortedMap<Object,Object> params = new TreeMap<Object,Object>(); params.put("appId", ConfigUtil.APPID); params.put("timeStamp", Long.toString(new Date().getTime())); params.put("nonceStr", PayCommonUtil.CreateNoncestr()); params.put("package", "prepay_id="+map.get("prepay_id")); params.put("signType", ConfigUtil.SIGN_TYPE); String paySign = PayCommonUtil.createSign("UTF-8", params); params.put("packageValue", "prepay_id="+map.get("prepay_id")); //这里用packageValue是预防package是关键字在js获取值出错 params.put("paySign", paySign); //paySign的生成规则和Sign的生成规则一致 params.put("sendUrl", ConfigUtil.SUCCESS_URL); //付款成功后跳转的页面 String userAgent = request.getHeader("user-agent"); char agent = userAgent.charAt(userAgent.indexOf("MicroMessenger")+15); params.put("agent", new String(new char[]{agent}));//微信版本号,用于前面提到的判断用户手机微信的版本是否是5.0以上版本。 String json = JSONObject.fromObject(params).toString(); return json; }
3 对支付成功微信通知的请求方法的修改
@RequestMapping("/paySuccess") public void paySuccess(HttpServletRequest request,HttpServletResponse response) throws Exception { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~"); outSteam.close(); inStream.close(); String result = new String(outSteam.toByteArray(),"utf-8");//获取微信调用我们notify_url的返回信息 Map<Object, Object> map = XMLUtil.doXMLParse(result); for(Object keyValue : map.keySet()){ System.out.println(keyValue+"="+map.get(keyValue)); } if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) { //TODO 对数据库的操作 String orderNo=map.get("out_trade_no").toString(); boolean ressult=orderService.updateOrderStatus(orderNo, 2); if(ressult){ System.out.println("数据库订单状态修改成功"); }else{ System.out.println("数据库订单状态修改失败"); } response.getWriter().write(PayCommonUtil.setXML("SUCCESS", "")); //告诉微信服务器,我收到信息了,不要在调用回调action了 System.out.println("-------------"+PayCommonUtil.setXML("SUCCESS", "")); } }
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
三 使用微信支付容易被坑到的几个细节点
1 有关微信支付开发配置部分
2 Oauth2.0网页版授权请记得配置回调页面域名
3 支付参数的问题点
这一步的问题主要在total_fee这个参数上面,这里应该是一个整型的字符串如“3500”、“1000”;但不能是“3500.0”或“3500.00”这种,且单位是分而不是元;其他参数应该不会出问题。
如果这些步骤都OK那么微信支付就不会有问题了,其他部分请参考如下链接
微信支付参考博客:http://blog.csdn.net/u011160656/article/details/41759195
消息处理参考博客:http://blog.csdn.net/lyq8479
看了这两位作者的博客之后个人觉得微信的常规开发应该是不成问题了。