微信公众号支付详细步骤(整理)
公司做公众号时需要接入微信支付,个人根据网上的demo摸索着完成了公司公众号的支付和退款功能。现也将代码分享出来,希望对需要朋友有帮助。
一.提交支付的toPay.jsp页面代码:
<% String basePath = request.getScheme() + "://"+ request.getServerName() + request.getContextPath()+ "/"; %><html><body> .... <div class="zdx3"><button onclick="pay()">共需支付${sumPrice }元 确认支付</button></div></body></html> <script> function pay() { var url="<%=basePath%>wechat/pay?money=${sumPrice}"; //注意此处的basePath是没有端口号的域名地址。如果包含:80, 在提交给微信时有可能会提示 “redirect_uri参数错误” 。 //money为订单需要支付的金额 //state中存放的为商品订单号 var weixinUrl="https://open.weixin.qq.com/connect/oauth2/authorize?appid=$ {appId}&redirect_uri="+encodeURI(url)+"&response_type=code&scope=snsapi_userinfo&state=$ {orderId}#wechat_redirect"; window.location.href=encodeURI(weixinUrl); }</script>
二.后台处理支付功能代码
(包含两部分:
1.处理支付信息,通过微信接口生成订单号,返回支付页面
2.提供一个微信支付完成后的回调接口)
第1部分代码:
/** * 用户提交支付,获取微信支付订单接口 */@RequestMapping(value="/pay")public ModelAndView pay(HttpServletRequest request,HttpServletResponse response) { ModelAndView mv = new ModelAndView(); String GZHID = "wxfd7c065eee11112222"; // 微信公众号id String GZHSecret = "b5b3a627f5d1f8888888888888"; // 微信公众号密钥id String SHHID = "111111111"; // 财付通商户号 String SHHKEY = "mmmmmmmmmmmmmmm"; // 商户号对应的密钥 /*------1.获取参数信息------- */ //商户订单号 String out_trade_no= request.getParameter("state"); //价格 String money = request.getParameter("money"); //金额转化为分为单位 String finalmoney = WeChat.getMoney(money); //获取用户的code String code = request.getParameter("code"); /*------2.根据code获取微信用户的openId和access_token------- */ //注: 如果后台程序之前已经得到了用户的openId 可以不需要这一步, 直接从存放openId的位置或session中获取就可以。 //toPay.jsp页面中提交的url路径也就不需要再经过微信重定向。 写成:http://localhost:8080/项目名/wechat/pay?money=${sumPrice}&state=${orderId} String openid=null; try { List<Object> list = accessToken(code); openid=list.get(1).toString(); } catch (IOException e) { logger.error("根据code获取微信用户的openId出现错误", e); mv.setViewName("error"); return mv; } /*------3.生成预支付订单需要的的package数据------- */ //随机数 String nonce_str= MD5.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes()); //订单生成的机器 IP String spbill_create_ip = request.getRemoteAddr(); //交易类型 :jsapi代表微信公众号支付 String trade_type = "JSAPI"; //这里notify_url是 微信处理完支付后的回调的应用系统接口url。 String notify_url ="http://69a6a38e.ngrok.natapp.cn/heyi-console/wechat/weixinNotify"; SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid", GZHID); packageParams.put("mch_id", SHHID); packageParams.put("nonce_str", nonce_str); packageParams.put("body", "费用"); packageParams.put("out_trade_no", out_trade_no); packageParams.put("total_fee", finalmoney); packageParams.put("spbill_create_ip", spbill_create_ip); packageParams.put("notify_url", notify_url); packageParams.put("trade_type", trade_type); packageParams.put("openid", openid); /*------4.根据package数据生成预支付订单号的签名sign------- */ RequestHandler reqHandler = new RequestHandler(request, response); reqHandler.init( GZHID, GZHSecret, SHHKEY); String sign = reqHandler.createSign(packageParams); /*------5.生成需要提交给统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder 的xml数据-------*/ String xml="<xml>"+ "<appid>"+ GZHID+"</appid>"+ "<mch_id>"+ SHHID+"</mch_id>"+ "<nonce_str>"+nonce_str+"</nonce_str>"+ "<sign>"+sign+"</sign>"+ "<body><![CDATA["+"费用"+"]]></body>"+ "<out_trade_no>"+out_trade_no+"</out_trade_no>"+ "<total_fee>"+finalmoney+"</total_fee>"+ "<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+ "<notify_url>"+notify_url+"</notify_url>"+ "<trade_type>"+trade_type+"</trade_type>"+ "<openid>"+openid+"</openid>"+ "</xml>"; /*------6.调用统一支付接口https: //api.mch.weixin.qq.com/pay/unifiedorder 生产预支付订单----------*/ String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String prepay_id=""; try { prepay_id = GetWxOrderno.getPayNo(createOrderURL, xml); if(prepay_id.equals("")){ mv.addObject("ErrorMsg", "支付错误"); mv.setViewName("error"); return mv; } } catch (Exception e) { logger.error("统一支付接口获取预支付订单出错", e); mv.setViewName("error"); return mv; } /*将prepay_id存到库中*/ PageData p = new PageData(); p.put("shopId", out_trade_no); p.put("prePayId", prepay_id); activityService.updatePrePayId(p); /*------ 7.将预支付订单的id和其他信息生成签名并一起返回到jsp页面 ------- */ nonce_str= MD5.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes()); SortedMap<String, String> finalpackage = new TreeMap<String, String>(); String timestamp = String.valueOf(System.currentTimeMillis() / 1000); String packages = "prepay_id="+prepay_id; finalpackage.put("appId", GZHID); finalpackage.put("timeStamp", timestamp); finalpackage.put("nonceStr", nonce_str); finalpackage.put("package", packages); finalpackage.put("signType", "MD5"); String finalsign = reqHandler.createSign(finalpackage); mv.addObject("appid", GZHID); mv.addObject("timeStamp", timestamp); mv.addObject("nonceStr", nonce_str); mv.addObject("packageValue", packages); mv.addObject("paySign", finalsign); mv.addObject("success","ok"); mv.setViewName("wechat/pay"); return mv; }
第2部分代码:
/** * 提交支付后的微信异步返回接口 */@RequestMapping(value="/weixinNotify") public void weixinNotify(HttpServletRequest request, HttpServletResponse response){ String out_trade_no=null; String return_code =null; try { 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); } outSteam.close(); inStream.close(); String resultStr = new String(outSteam.toByteArray(),"utf-8"); logger.info("支付成功的回调:"+resultStr); Map<String, Object> resultMap = parseXmlToList(resultStr); String result_code = (String) resultMap.get("result_code"); String is_subscribe = (String) resultMap.get("is_subscribe"); String transaction_id = (String) resultMap.get("transaction_id"); String sign = (String) resultMap.get("sign"); String time_end = (String) resultMap.get("time_end"); String bank_type = (String) resultMap.get("bank_type"); out_trade_no = (String) resultMap.get("out_trade_no"); return_code = (String) resultMap.get("return_code"); request.setAttribute("out_trade_no", out_trade_no); //通知微信.异步确认成功.必写.不然微信会一直通知后台.八次之后就认为交易失败了. response.getWriter().write(RequestHandler.setXML("SUCCESS", "")); } catch (Exception e) { logger.error("微信回调接口出现错误:",e); try { response.getWriter().write(RequestHandler.setXML("FAIL", "error")); } catch (IOException e1) { e1.printStackTrace(); } } if(return_code.equals("SUCCESS")){ //支付成功的业务逻辑 }else{ //支付失败的业务逻辑 } }
三.微信app中具体支付的jsp页面
<html><head><script src="js/jquery-1.8.2.min.js" type="text/javascript"> </script></head><body "pay();"> <script type="text/javascript"> function pay(){ 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(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId" : "${appid}", //公众号名称,由商户传入 "timeStamp": "${timeStamp}", //时间戳,自1970年以来的秒数 "nonceStr" : "${nonceStr}", //随机串 "package" : "${packageValue}", "signType" : "MD5", //微信签名方式: "paySign" : "${paySign}" //微信签名 },function(res){ if(res.err_msg == "get_brand_wcpay_request:ok"){ alert("微信支付成功!"); }else if(res.err_msg == "get_brand_wcpay_request:cancel"){ alert("用户取消支付!"); }else{ alert("支付失败!"); } }); } </script></body></html>
其他需要用到的相关类和方法:
金额 元转分:
/** * 元转换成分 * @param money * @return */ public static String getMoney(String amount) { if(amount==null){ return ""; } // 金额转化为分为单位 String currency = amount.replaceAll("\\$|\\¥|\\,", ""); //处理包含, ¥ 或者$的金额 int index = currency.indexOf("."); int length = currency.length(); Long amLong = 0l; if(index == -1){ amLong = Long.valueOf(currency+"00"); }else if(length - index >= 3){ amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", "")); }else if(length - index == 2){ amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0); }else{ amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00"); } return amLong.toString(); }
通过微信用户code获取用户的openId:
/** * 通过微信用户的code换取网页授权access_token * @return * @throws IOException * @throws */ public List<Object> accessToken(String code) throws IOException { List<Object> list = new ArrayList<Object>(); String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WeChat.HYGZHID + "&secret=" + WeChat.HYGZHSecret+ "&code=" + code + "&grant_type=authorization_code"; HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost(url); HttpResponse res = client.execute(post); if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpEntity entity = res.getEntity(); String str = org.apache.http.util.EntityUtils.toString(entity, "utf-8"); ObjectMapper mapper=new com.fasterxml.jackson.databind.ObjectMapper.ObjectMapper(); Map<String,Object> jsonOb=mapper.readValue(str, Map.class); list.add(jsonOb.get("access_token")); list.add(jsonOb.get("openid")); } return list; }
MD5提取摘要:
/** * MD5加密 */public class MD5 { private MD5() {} /** * 对传入的数据提取摘要 * @param buffer * @return */ public final static String getMessageDigest(byte[] buffer) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; try { MessageDigest mdTemp = MessageDigest.getInstance("MD5"); mdTemp.update(buffer); byte[] md = mdTemp.digest(); int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { return null; } } }
解析微信回调的xml数据:
/** * description: 解析微信通知xml * * @param xml * @return * @author ex_yangxiaoyi * @see */ @SuppressWarnings({ "unused", "rawtypes", "unchecked" }) private static Map parseXmlToList(String xml) { Map retMap = new HashMap(); try { StringReader read = new StringReader(xml); // 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入 InputSource source = new InputSource(read); // 创建一个新的SAXBuilder SAXBuilder sb = new org.jdom.input.SAXBuilder.SAXBuilder(); // 通过输入源构造一个Document Document doc = (Document) sb.build(source); Element root = doc.getRootElement();// 指向根节点 List<Element> es = root.getChildren(); if (es != null && es.size() != 0) { for (Element element : es) { retMap.put(element.getName(), element.getValue()); } } } catch (Exception e) { e.printStackTrace(); } return retMap; }
java文件和需要用到的工具类,我都放到csdn的下载中了。
地址:http://download.csdn.net/detail/aofavx/9606697。
如果有不对或者少文件的地方,请留言。我给大家补正。