一、前言
这次的项目主要是关于微信公众号的一个开发,本人这次分配的模块是后台微信公众号的支付和退款,第一次接触微信公众的项目刚开始一脸懵逼,开发过程中遇到各种坑,所以想自己写一篇详细的关于微信公众号的开发,希望能对小伙伴们有所帮助!
二、统一支付接口
支付开发文档:微信公众支付开发文档
支付流程:首先由前端调用后台的统一支付接口,通过统一支付接口拿到返回参数,主要是要拿到前端调用支付时的prepay_id,将统一支付接口返回的参数进行签名、封装之后返回给前端,前端通过H5或者JSSDK调用微信支付功能,调用成功后微信会根据统一支付接口配置的通知URL来调用开发中的通知接口,同时微信端会将数据发送到通知接口来,然后在通知接口中做其他业务的处理就可以了,处理成功给微信端返回成功状态码,失败返回失败状态码。
统一下单接口代码:
/** * * @Title: wxPay * @Description: 微信统一支付 * @param request * @param response * @throws Exception * @return Map<String,String> */ @RequestMapping("/wxPay") @ResponseBody public JsPayResult wxPay(HttpServletRequest request, HttpServletResponse response) throws Exception { String usid = request.getParameter("usid"); Account ac = userCache.get(usid); // 交易类型 String trade_type = "JSAPI"; // 用户标识 String openid = ac.getOpenid(); // 公众账号ID String appid = Constants.APPID; // 商户号 String mch_id = Constants.MCHID; // 通知地址 String notify_url = Constants.PAY_NOTIFY_URL; // 随机字符串 String nonce_str = CommonUtil.getRandomStr(); // 商品描述 String body = request.getParameter("body"); //String body = "测试"; // 终端IP String spbill_create_ip = request.getRemoteAddr(); // 商户订单号 String out_trade_no = request.getParameter("out_trade_no"); //String out_trade_no = "111"; // 金额 String total_fee = CommonUtil.getMoney(request.getParameter("total_fee")); //String total_fee = CommonUtil.getMoney("0.01"); //String total_fee = "1"; // 将请求参数封装至Map集合中 SortedMap<String, String> paramMap = new TreeMap<String, String>(); paramMap.put("appid", appid); paramMap.put("mch_id", mch_id); paramMap.put("nonce_str", nonce_str); paramMap.put("body", body); paramMap.put("out_trade_no", out_trade_no); paramMap.put("total_fee", total_fee); paramMap.put("spbill_create_ip", spbill_create_ip); paramMap.put("notify_url", notify_url); paramMap.put("trade_type", trade_type); paramMap.put("openid", openid); logger.info("支付IP" + spbill_create_ip); // 签名 String sign = SignUtil.createSign(paramMap, Constants.PARTNER_KEY); paramMap.put("sign", sign); // 请求的xml数据 String requestXml = XMLUtil.map2Xml(paramMap,"xml"); // 调用post请求,同时返回的xml数据 String resposeXmL = CommonUtil.weChatPayhttpsRequest(Constants.ORDER_PAY_URL, Constants.POST, requestXml); Map<String, String> responseMap = XMLUtil.xml2Map(resposeXmL); SortedMap<String, String> rspMap = new TreeMap<String, String>(); JsPayResult result = new JsPayResult(); if (Constants.RETURN_CODE.equals(responseMap.get("return_code"))) { String nonceStr = CommonUtil.getRandomStr(); String timeStamp = CommonUtil.createTimeStamp(); result.setAppId(responseMap.get("appid")); result.setTimeStamp(timeStamp); result.setNonceStr(nonceStr); result.setSignType("MD5"); rspMap.put("appId", responseMap.get("appid")); rspMap.put("timeStamp",timeStamp); rspMap.put("nonceStr",nonceStr ); rspMap.put("signType", "MD5"); if (Constants.RESULT_CODE.equals(responseMap.get("result_code"))) { result.setPackaged("prepay_id=" + responseMap.get("prepay_id")); rspMap.put("package", result.getPackaged()); String paySign = SignUtil.createSign(rspMap, Constants.PARTNER_KEY); result.setPaySign(paySign); result.setResultCode(Constants.SUCCESS_CODE); result.setMessage("支付成功!"); } logger.info("*****统一支付:支付成功!*****"+responseMap.get("return_code")+"--"+responseMap.get("return_msg")); } else { result.setResultCode(Constants.FAIL_CODE); result.setMessage("签名失败!"); logger.info("*****统一支付:签名失败!*****"+responseMap.get("return_code")+"--"+responseMap.get("return_msg")); } return result; }
微信支付请求:
/** * * @Title: weChatPayhttpsRequest @Description: 微信支付请求 @param @param * requestUrl @param @param requestMethod @param @param * outputStr @param @return @return String @throws */ public static String weChatPayhttpsRequest(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) { ce.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return buffer.toString(); }
签名方法:
/** * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 */ public static String createSign(SortedMap<String, String> packageParams,String key) { StringBuffer sb = new StringBuffer(); Set<Entry<String,String>> es = packageParams.entrySet(); Iterator<Entry<String, String>> it = es.iterator(); while (it.hasNext()) { Map.Entry<String,String> entry = it.next(); String pName = entry.getKey(); String pValue = entry.getValue(); if (StringUtils.isNotBlank(pValue) && !"sign".equals(pName)&& !"key".equals(pName)) { sb.append(pName).append("=").append(pValue).append("&"); } } sb.append("key=" + key); String sign = MD5Util.MD5Encode(sb.toString(), "utf-8").toUpperCase(); return sign; }
将返回的xml转换成map:
public static Map<String, String> xml2Map(String xml) { // 将解析结果存储在HashMap中 HashMap<String, String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); InputStream xmlIs; Document document = null; try { xmlIs = new ByteArrayInputStream(xml.getBytes("utf-8")); document = reader.read(xmlIs); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } // 得到xml根元素 Element root = document.getRootElement(); recursiveParseXML(root,map); return map; }
三、支付结果通知接口
支付结果通知接口是是统一支付接口中配置的,主要是为了获取微信端的交易订单号,同时在支付结果通知中处理其他的业务逻辑,处理完成后需要给微信端返回成功码,如果返回失败码微信端会一直发送微信支付结果通知,发送次数达到一定数量后就停止发送。
支付结果通知接口:
/** * * @Title: notify * @Description: 微信支付结果通知 * @param @param request * @param @param response * @param @return * @param @throws Exception * @return Map<String,String> * @throws */ @ResponseBody @RequestMapping("/notify") public JsPayResult notify(HttpServletRequest request, HttpServletResponse response) throws Exception { JsPayResult result = new JsPayResult(); logger.info("开始处理支付返回的请求"); // 微信支付系统发送的数据(<![CDATA[product_001]]>格式) Map<String, String> xmlMap = CommonUtil.parseXmlForPay(request); logger.info("微信支付系统发送的数据" + xmlMap); // 返回给微信服务器的xml String respXml = ""; // 判断返回是否成功 if (Constants.RETURN_CODE.equals(xmlMap.get("return_code"))) { String time_end = xmlMap.get("time_end"); ResponseResult r = payRegistration(Constants.BRANCHCODE,xmlMap.get("out_trade_no"),xmlMap.get("transaction_id"), Constants.PAYMODE,xmlMap.get("total_fee"),DateUtil.getTradeTime(time_end),Constants.YYSOURCE); if (Constants.RESULTCODE.equals(r.getResultCode())) { respXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; result.setMessage("支付成功!"); logger.info("*******支付结果通知**********"+"支付成功!"+xmlMap.get("return_code") + "**************" + xmlMap.get("return_msg")); } else { respXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[ERROR]]></return_msg>" + "</xml> "; result.setMessage("支付失败!"); logger.error("*******支付结果通知**********"+"支付失败!"+xmlMap.get("return_code") + "**************" + xmlMap.get("return_msg")); } result.setResultCode(Constants.SUCCESS_CODE); } else { respXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" + xmlMap.get("return_code") + "]]></return_msg>" + "</xml> "; result.setResultCode(Constants.FAIL_CODE); result.setMessage("支付失败!"); logger.error("*******支付结果通知**********"+"支付失败!"+xmlMap.get("return_code") + "**************" + xmlMap.get("return_msg")); } BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(respXml.getBytes()); out.flush(); out.close(); return result; }