微信公众号之H5支付笔记

   以前一直没有接触支付这一块,突然接到要做公众号的支付着实让我头疼了几天,下面就说一说微信公众号H5支付的一些具体流程和心得(当然其中没少借鉴其他大牛们的文章,看了很多才搞定,也是着实对自己着急)。

        首先,我们第一步肯定是打开官网提供的微信支付开发者文档,文档打开后大致浏览一遍进入开发阶段,下面是微信支付提供的业务流程时序图:

微信内网页支付时序图


看上面的流程时序图呢,我们可以看到我们要做的其实不是很多,实现起来其实没有预想的那么麻烦,下面来看看流程。


第一步:设置支付目录和授权域名


        微信开发文档里面开发步骤已经详细讲解了设置的详细步骤,这里不再做累赘的陈述,需要注意一点的是,授权目录指的是你调用微信支付SDK接口的页面所在的目录。


第二步:生成商户订单,调用统一下单接口

商户订单可以自己生成,统一下单接口需要参数大致有下面一些是必须要传给微信
/**
 * 支付信息实体类
 * @author luhanlin
 * @time   2017-12-01
 */
public class UnifiedOrderParams{
	
	private String appid;  			//微信支付分配的公众账号ID
	private String mch_id;			//微信支付分配的商户号
	private String out_trade_no;		//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
	private String body;			//商品简单描述
	private Integer total_fee;		//订单总金额,单位为分
	private String nonce_str;		//随机字符串
	private String spbill_create_ip;	//APP和网页支付提交用户端ip
	private String trade_type;		//取值如下:JSAPI,NATIVE,APP等 这里取JSAPI
	private String openid;			//trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识
	private String notify_url; 		//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。

}

其中appId和mchId可以自己去微信公众平台申请,最主要的是openId需要我们自己去获取因此,我们还需要去获取openid,怎
么获取openid呢?
我们点开微信提供给我们的文档 → 打开参考信息链接 → 点击微信网页开发→ 点击微信网页授权

  其中有两种授权的调用,这两种授权的区别在于一种是不弹提示,一种需要用户点击确定完成授权,静默只能获取openid,而非静默授权可以获取token进行用户信息的调用。

继续往下看,可以看到获取流程:

  其实我们前两步骤已经可以获取openid了,必须要注意的是公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器。openid获取到以后其实我们已经快要成功了。以下是代码(MVC结构框架),实体和工具类我会在后续贴出:

	/**
	 * 返回回调URL给需要的页面,获取code
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping("/XXX")
	public String oauthBind(HttpServletRequest request, HttpServletResponse response){
		AuthCodeParams authCodeParams = new AuthCodeParams();
		//后台设置的回调地址,可以获取code
		authCodeParams.setRedirect_uri("");
		//微信支付分配的公众账号ID(企业号corpid即为此appId)
		authCodeParams.setAppid("");
		//采用非静默授权
		authCodeParams.setScope(AuthCodeParams.SCOPE_SNSPAIUSERINFO);
		//防止被攻击,用于校验
		//authCodeParams.setState(MD5Utils.MD5("ceshi")); 
		String url = null;
		try {   /**此处url拼接到如下:
		 	* "https://open.weixin.qq.com/connect/oauth2/authorize?
			 * appid=APPID&redirect_uri=http://你的域名/weChatpay/mainServlet&response_type=code
			 * &scope=snsapi_userinfo&state=123#wechat_redirect"
			 */
			url = wechatAuthService.getAuthPath(authCodeParams, WeChatConfig.OAUTH_AUTHORIZE_URL);
			request.setAttribute("bindurl", url);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//String result = TheMethodUtils.doGet(url);
		System.out.println("请求的url为:\n"+url);
		return url;
	}

	/**
	 * 授权进入支付页面
	 *
	 * @param request
	 * @param response
	 * @param url
	 * @return
	 * @throws Exception
	 */
	@RequestMapping(value = "XXX", method = { RequestMethod.GET })
	@ResponseBody
	public AjaxResult XXX(HttpServletRequest request, HttpServletResponse response,String code) throws Exception {
		AjaxResult result = new AjaxResult();
		
	    AuthAccessToken authAccessToken = null;
	    //String code = request.getParameter("code");//可用redis保存
	    //System.out.println("********************code是:"+code);
	    if(code==null){
	         return null;
	    }
	    
	    AuthTokenParams authTokenParams = new AuthTokenParams();
	    authTokenParams.setAppid("");
	    authTokenParams.setSecret("");
	    authTokenParams.setCode(code);
	    //String state = request.getParameter("state");//state可以进行验证,此处不做处理/**访问的URL如下
		 * "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appid+"
		 * &secret="+appsecret+"&code="+code+"&grant_type=authorization_code";
		 */
	    authAccessToken = service.getAuthAccessToken(authTokenParams, WeChatConfig.OAUTH_ACCESS_TOKEN_URL);  //ACCESS_TOKEN_URL
	    if(authAccessToken!=null){
	    	/**
			 * 出现无效code时的处理
			 * {"errcode":40029,"errmsg":"invalid code, hints: [ req_id: xKrvuA0590th36 ]"}
			 */
	    	if (authAccessToken.getErrcode()!=null||"".equals(authAccessToken.getErrcode())) {
				//result.setStatus(StatusCode.FAILURE);
				//result.setMessage(authAccessToken.getErrmsg());
				return result;
			}
	    	//将当前用户信息保存到本地数据库中,
	    	//用户可以保存用户信息,自身业务处理
	    	
	    }else {
			result.setMessage("获取用户openid失败");
		}
	    return result;
	}

进行当前这一步我们就获取到了openid了,可以进行统一下单了。但是,但是我们进入最坑的一步了有没有,微信要求数据的收发都是xml形式,并且签名都是需要排序和加密,参数也得是固定大小写,需要多次签名才可以验证成功,当然这也是微信支付的安全防范无可厚非,下面进入正题

     /**
     * 微信内H5调起支付
     *
     * @param request
     * @param response
     * @param openId
     * @return
     * @throws Exception
     */
    @RequestMapping("/xxxx")
    @ResponseBody
    public AjaxResult xxxxx(HttpServletRequest request, HttpServletResponse response,UnifiedOrderParams params) throws Exception {
    	AjaxResult result = new AjaxResult();
        JsPayResult jspay = null;
        logger.info("****正在支付的openId****" + params.getOpenid());
        // 统一下单
        String out_trade_no = PayUtil.createOutTradeNo();;//PayUtil.createOutTradeNo();
        //String spbill_create_ip = "192.168.134.1";//HttpReqUtil.getIpAddress(request);
        logger.info("支付IP" + params.getSpbill_create_ip());
        String nonce_str = PayUtil.createNonceStr(32); // 随机数据
        //参数组装
        params.setAppid("");// 必须   
        params.setMch_id("");// 必须
        params.setOut_trade_no(out_trade_no);// 必须
        params.setNonce_str(nonce_str); // 必须  随机字符串
        params.setSpbill_create_ip(""); // 必须
        params.setTrade_type("JSAPI"); // 必须
        params.setNotify_url("");// 异步通知url
        //-----测试增加支付挡板,此处为设置前金额----
        Integer total_fee = params.getTotal_fee()*100;
        params.setTotal_fee(total_fee);
        
        // 统一下单 请求的Xml(正常的xml格式)
        String unifiedXmL = jsPayService.abstractPayToXml(params);////签名并入service
        //System.out.println("unifiedXmL======"+unifiedXmL);
        // 返回<![CDATA[SUCCESS]]>格式的XML
        //String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.POST_METHOD,WeChatConfig.UNIFIED_ORDER_URL, null, unifiedXmL);
        String unifoedResult = PayCommonUtil.httpsRequest(WeChatConfig.UNIFIED_ORDER_URL, "POST",unifiedXmL);  
        System.out.println("unifoedResult========="+unifoedResult); 
        
        // 验证签名
        if (Signature.checkIsSignValidFromResponseString(unifoedResult,properConfig.wechat_api_key)) {
	        Map<String, Object> map = null;  
	        map = PayCommonUtil.doXMLParse(unifoedResult);
	        if("".equals(map.get("prepay_id"))){
	        	result.setMessage("统一支付接口获取预支付订单出错");
	        	return result;
	            //request.setAttribute("ErrorMsg", "统一支付接口获取预支付订单出错");
	            //response.sendRedirect("error.jsp");
	        }
	        //生成JSAPI需要的参数前,在系统生成订单
	        try {
	        	//可以在此处处理订单信息,也可以在前面调用统一下单接口前生成,
			//
			} catch (Exception e) {
				logger.info("系统保存订单出错");
				result.setMessage("系统保存订单出现异常");
				return result;
			}
	        //生成JSAPI需要参数信息
	        jspay = jsPayService.getJsApiParams(map);
	        Map<String, Object> params = new HashMap<String, Object>();
		JsPayResult result = new JsPayResult();
              map.put("appId", "");
map.put("timeStamp", PayUtil.createTimeStamp());
map.put("nonceStr", (String)map.get("nonce_str"));
map.put("package", "prepay_id=" + map.get("prepay_id"));
map.put("signType", "MD5");
        result.setAppId("");
        result.setTimeStamp(PayUtil.createTimeStamp());
        //随机字符串,不长于32位
        result.setNonceStr((String)map.get("nonce_str"));
        //订单详情扩展字符串
        result.setPackageStr("prepay_id=" + map.get("prepay_id"));
        //进行签名
        String paySign = null;
paySign = Signature.getSign(params, api_key);//api_key是在商户号中设置的
        result.setPaySign(paySign);
        
        result.setResultCode("SUCCESS"); result.put("PARAMS", jspay); } else { logger.info("签名验证错误"); } return result; }


    上面代码可能还是有点乱,大家自行整理一下,要说明的一点就是签名完成后一定要到官网上面进行验证一下,否则纠结半天都不知道啥问题。

下单完成后,可以进行微信内H5调起支付了。


最后就是微信的支付结果的通知了,我们在notify_url已经设置了值,微信会通过GET请求访问我们的接口,以下是代码信息:

/**
     * 微信支付结果通知(统一下单参数的notify_url)
     * @author luhanlin
     * @date  2017-12-01
     *
     */
    @ResponseBody
    @RequestMapping("/xxx")
    public AjaxResult notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	AjaxResult ajaxResult = new AjaxResult();
        logger.info("开始处理支付返回的请求");
        String resXml = ""; // 反馈给微信服务器
        String notifyXml = HttpReqUtil.inputStreamToStrFromByte(request.getInputStream());// 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
        logger.debug("微信支付系统发送的数据"+notifyXml);
        
        /**
         * 微信支付系统发送的数据<xml><appid><![CDATA[xxxxxx]]></appid>
							<bank_type><![CDATA[CFT]]></bank_type>
							<cash_fee><![CDATA[1]]></cash_fee>
							<fee_type><![CDATA[CNY]]></fee_type>
							<is_subscribe><![CDATA[Y]]></is_subscribe>
							<mch_id><![CDATA[xxxxxx]]></mch_id>
							<nonce_str><![CDATA[xxxxxxxx]]></nonce_str>
							<openid><![CDATA[xxxxxxxxxxx]]></openid>
							<out_trade_no><![CDATA[xxxxxxxxxx]]></out_trade_no>
							<result_code><![CDATA[SUCCESS]]></result_code>
							<return_code><![CDATA[SUCCESS]]></return_code>
							<sign><![CDATA[xxxxxxx]]></sign>
							<time_end><![CDATA[xxxxxxxx]]></time_end>
							<total_fee>1</total_fee>
							<trade_type><![CDATA[JSAPI]]></trade_type>
							<transaction_id><![CDATA[xxxxxxxxx]]></transaction_id>
							</xml>
         */
        // 验证签名
        if (Signature.checkIsSignValidFromResponseString(notifyXml,api_key)) {
            PayNotifyResult notify = new PayNotifyResult();
            Map<String, Object> map = PayCommonUtil.doXMLParse(notifyXml);
            notify = JavaBeanUtil.mapToBean(map, notify);
            logger.debug("支付结果" + notify.toString());
            if ("SUCCESS".equals(notify.getResult_code())) {
                /**** 业务逻辑  ****/
                
                // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {
                logger.info("支付失败,错误信息:" + notify.getErr_code_des());
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" + notify.getErr_code_des() + "]]></return_msg>" + "</xml> ";
            }
        } else {
        	ajaxResult.setStatus(StatusCode.FAILURE);// 支付失败
        	ajaxResult.setMessage("签名验证错误");
            logger.info("签名验证错误");
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
        }
 
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
        return ajaxResult;
    }

到此为止,微信支付就成功了。^_^


下面是一些工具类代码,实体大家还是看看微信官方文档,可以更好的去熟悉微信支付,工具可能不是很全,挺多都是可以在网

上找到的,上面代码希望大佬们不要喷我哦,我只是一只小萌新,么么哒。^_^

public class WeChatConfig {

	//微信支付分配的公众账号ID(企业号corpid即为此appId)
	//public static final String APP_ID = "xxxxxxx";
	//密码
	//public static final String APP_SECRET = "xxxxxxxx";
	//微信支付分配的商户号
	//public static final String MCH_ID = "xxxxxxxxxx";
	//key为商户平台设置的密钥key
	//public static final String API_KEY = "xxxxxxxxxxxxxxx";
	//服务号的Token令牌
	//public static final String WECHAT_TOKEN = "xx";
	
	// 授权链接
	public static final String OAUTH_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
	// 获取token的链接
	public static final String OAUTH_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
	// 刷新token的链接
	public static final String OAUTH_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
	// 获取授权用户信息的链接
	public static final String SNS_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo";
	public static final String SNS_USERINFO_URL2 = "https://api.weixin.qq.com/cgi-bin/user/info";
	
	// 判断用户accessToken是否有效
	public static final String SNS_AUTH_URL = "https://api.weixin.qq.com/sns/auth";
	
	//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
	public static final String NOTIFY_URL = "xxxxxxxxxxxxxxxxxxxx";
	//微信支付订单查询链接
	public static final String ORDER_CHECK_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
	
	//统一下单生成预订单的链接
	public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	
	//获取code的接受地址
	public static final String REDIRECT_URI = "xxxxxxxxxxxxxx";
	
	
}

public class HttpReqUtil {

	
	public static String inputStreamToStrFromByte(ServletInputStream inputStream) {
		byte[] bytes = new byte[1024 * 1024];  
        //InputStream is = request.getInputStream();  
		String str = null;
        int nRead = 1;  
        int nTotalRead = 0;  
        while (nRead > 0) {  
            try {
				nRead = inputStream.read(bytes, nTotalRead, bytes.length - nTotalRead);
				if (nRead > 0){
					nTotalRead = nTotalRead + nRead;  
				}  
				str = new String(bytes, 0, nTotalRead, "utf-8");  
			} catch (IOException e) {
				e.printStackTrace();
			}  
        }  
        System.out.println("Str:" + str); 
		return str;
	}

	/** 
     * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址, 
     * 参考文章: http://developer.51cto.com/art/201111/305181.htm 
     *  
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢? 
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。 
     *  
     * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, 
     * 192.168.1.100 
     *  
     * 用户真实IP为: 192.168.1.110 
     *  
     * @param request 
     * @return 
     */  
    public static String getIpAddress(HttpServletRequest request) {  
        String ip = request.getHeader("x-forwarded-for");  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("Proxy-Client-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("WL-Proxy-Client-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("HTTP_CLIENT_IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getRemoteAddr();  
        }  
        
        if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip))  
            try {  
                ip = InetAddress.getLocalHost().getHostAddress();  
            }  
            catch (UnknownHostException unknownhostexception) {  
            }  
        return ip;  
    }  
	
	public static String getRemortIP(HttpServletRequest request) {
		
		String ip = request.getHeader("X-Forwarded-For");
		if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			// 多次反向代理后会有多个ip值,第一个ip才是真实ip
			int index = ip.indexOf(",");
			if (index != -1) {
				return ip.substring(0, index);
			} else {
				return ip;
			}
		}
		ip = request.getHeader("X-Real-IP");
		if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			return ip;
		}
		return request.getRemoteAddr();
	}

	public static String urlEncode(String str, String characterEncoding) {
		String encode =null;
		try {
			encode = URLEncoder.encode(str, characterEncoding);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
		return encode;
	}
	
	private static String urlDecode(String str, String characterEncoding) {
		String decode =null;
		try {
			decode = URLDecoder.decode(str, characterEncoding);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
		return decode;
		
	}
	
	/**
	 * 发送POST请求:方法一 
	 * 
	 */
	public static String sendPost(String url, JSONObject json) {
		Iterator iterator = json.keys();
		PrintWriter out = null;
		BufferedReader in = null;
		String result = "";
		try {
			URL realUrl = new URL(url);
			// 打开和URL之间的连接
			URLConnection conn = realUrl.openConnection();
			// 设置通用的请求属性
			conn.setRequestProperty("accept", "*/*");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("user-agent",
					"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			// conn.setRequestProperty("Content-type",
			// "application/json;charset=utf-8");
			// 发送POST请求必须设置如下两行
			conn.setDoOutput(true);
			conn.setDoInput(true);
			// 获取URLConnection对象对应的输出流
			out = new PrintWriter(new OutputStreamWriter(
					conn.getOutputStream(), "utf-8"));
			// 发送请求参数
			String param = "";
			while (iterator.hasNext()) {
				String key = (String) iterator.next();
				String value = json.getString(key);
				param += key + "=" + value + "&";
			}
			if (param.length() > 1)
				param = param.substring(0, param.length() - 1);
			out.print(param);
			// flush输出流的缓冲
			out.flush();
			// 定义BufferedReader输入流来读取URL的响应
			in = new BufferedReader(new InputStreamReader(
					conn.getInputStream(), "UTF-8"));
			String line;
			while ((line = in.readLine()) != null) {
				result += line;
			}
		} catch (Exception e) {
			System.out.println("发送 POST 请求出现异常!" + e);
			e.printStackTrace();
		}
		// 使用finally块来关闭输出流、输入流
		finally {
			try {
				if (out != null) {
					out.close();
				}
				if (in != null) {
					in.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 组装url
	 * @param params
	 * @param path
	 * @param string
	 * @return
	 */
	public static String setParmas(Map<String, String> params, String path) {
		/**
		 *       
		 *	https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=要跳转的下订单的url
		 *	&response_type=code&scope=snsapi_base&state=123#wechat_redirect
		 */
		StringBuffer buffer = new StringBuffer();
		buffer.append(path+"?");
		for (Entry<String, String> entry : params.entrySet()) {  
			buffer.append(entry.getKey() + "=" + entry.getValue());  
			buffer.append("&");  
	    }  
	    String s = buffer.toString();  
	    if (s.endsWith("&")) {  
	        s = StringUtils.substringBeforeLast(s, "&");  
	    }  
		
		return s;
	}

	/**
	 * 拼接url
	 * @param object
	 * @param path
	 * @return
	 */
	public static String getUrl(Object object, String path) {
		Method[] methods = object.getClass().getDeclaredMethods();
		//拼接用的buffer
		StringBuffer buffer = new StringBuffer();
		buffer.append(path);
		buffer.append("?");
        for (Method method : methods) {
            try {
                if (method.getName().startsWith("get")) {
                    String field = method.getName();
                    field = field.substring(field.indexOf("get") + 3);
                    field = field.toLowerCase().charAt(0) + field.substring(1);
                    Object value = method.invoke(object, (Object[]) null);
                    
                    buffer.append(field + "=" + value);  
    				buffer.append("&");
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        path = buffer.toString();  
	    if (path.endsWith("&")) {  
	    	path = StringUtils.substringBeforeLast(path, "&");  
	    } 
		return path;
	}

}

public class JavaBeanUtil {

	/** 
	* 将对象装换为map 
	* @param bean 
	* @return 
	*/  
	public static <T> Map<String, Object> beanToMap(T bean) {  
	  Map<String, Object> map = new HashMap<String, Object>();  
	  if (bean != null) {  
	      BeanMap beanMap = BeanMap.create(bean);  
	      for (Object key : beanMap.keySet()) {  
	          map.put(key+"", beanMap.get(key));  
	      }             
	  }  
	  return map;  
	}  
	
	/** 
	* 将map装换为javabean对象 
	* @param map 
	* @param bean 
	* @return 
	*/  
	public static <T> T mapToBean(Map<String, Object> map,T bean) {  
	  BeanMap beanMap = BeanMap.create(bean);  
	  beanMap.putAll(map);  
	  return bean;  
	}  
	
	/** 
	* 将List<T>转换为List<Map<String, Object>> 
	* @param objList 
	* @return 
	* @throws JsonGenerationException 
	* @throws JsonMappingException 
	* @throws IOException 
	*/  
	public static <T> List<Map<String, Object>> objectsToMaps(List<T> objList) {  
	  List<Map<String, Object>> list = new ArrayList<>();  
	  if (objList != null && objList.size() > 0) {  
	      Map<String, Object> map = null;  
	      T bean = null;  
	      for (int i = 0,size = objList.size(); i < size; i++) {  
	          bean = objList.get(i);  
	          map = beanToMap(bean);  
	          list.add(map);  
	      }  
	  }  
	  return list;  
	}  
	
	/** 
	* 将List<Map<String,Object>>转换为List<T> 
	* @param maps 
	* @param clazz 
	* @return 
	* @throws InstantiationException 
	* @throws IllegalAccessException 
	*/  
	public static <T> List<T> mapsToObjects(List<Map<String, Object>> maps,Class<T> clazz) throws InstantiationException, IllegalAccessException {  
	  List<T> list = new ArrayList<>();  
	  if (maps != null && maps.size() > 0) {  
	      Map<String, Object> map = null;  
	      T bean = null;  
	      for (int i = 0,size = maps.size(); i < size; i++) {  
	          map = maps.get(i);  
	          bean = clazz.newInstance();  
	          mapToBean(map, bean);  
	          list.add(bean);  
	      }  
	  }  
	  return list;  
	}
}

/** 
 * MD5加密工具类 
 * <功能详细描述> 
 *  
 * @author  chenlujun 
 * @version  [版本号, 2014年10月1日] 
 * @see  [相关类/方法] 
 * @since  [产品/模块版本] 
 */  
public abstract class MD5Utils  
{  
    public final static String MD5(String pwd) {  
        //用于加密的字符  
        char md5String[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',  
                'A', 'B', 'C', 'D', 'E', 'F' };  
        try {  
            //使用平台的默认字符集将此 String 编码为 byte序列,并将结果存储到一个新的 byte数组中  
            byte[] btInput = pwd.getBytes();  
               
            //信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。  
            MessageDigest mdInst = MessageDigest.getInstance("MD5");  
               
            //MessageDigest对象通过使用 update方法处理数据, 使用指定的byte数组更新摘要  
            mdInst.update(btInput);  
               
            // 摘要更新之后,通过调用digest()执行哈希计算,获得密文  
            byte[] md = mdInst.digest();  
               
            // 把密文转换成十六进制的字符串形式  
            int j = md.length;  
            char str[] = new char[j * 2];  
            int k = 0;  
            for (int i = 0; i < j; i++) {   //  i = 0  
                byte byte0 = md[i];  //95  
                str[k++] = md5String[byte0 >>> 4 & 0xf];    //    5   
                str[k++] = md5String[byte0 & 0xf];   //   F  
            }  
               
            //返回经过加密后的字符串  
            return new String(str);  
               
        } catch (Exception e) {  
            return null;  
        }  
    } 
    
/**   
 * @author create by yaoyuan   
 * @date 2017年6月5日 下午8:13:09 
 */


    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" };

    
    public static void main(String[] args) {
		System.out.println(MD5Utils.MD5("luhanlin"));
	}
}

public class PayCommonUtil {  
    //随机字符串生成  
    public static String getRandomString(int length) { //length表示生成字符串的长度      
           String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";         
           Random random = new Random();         
           StringBuffer sb = new StringBuffer();         
           for (int i = 0; i < length; i++) {         
               int number = random.nextInt(base.length());         
               sb.append(base.charAt(number));         
           }         
           return sb.toString();         
        }    
    //请求xml组装  
      public static String getRequestXml(SortedMap<String,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 key = (String)entry.getKey();  
                String value = (String)entry.getValue();  
                if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {  
                    sb.append("<"+key+">"+"<![CDATA["+value+"]]></"+key+">");  
                }else {  
                    sb.append("<"+key+">"+value+"</"+key+">");  
                }  
            }  
            sb.append("</xml>");  
            return sb.toString();  
        }  
      //生成签名  
      public static String createSign(String api_key,SortedMap<String,Object> parameters){  
            StringBuffer sb = new StringBuffer();  
            Set es = parameters.entrySet();  
            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=" + api_key);  
            String sign = MD5Utils.MD5(sb.toString()).toUpperCase();  
            return sign;  
        }  
      //请求方法  
      public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {  
            try {  
                 
                URL url = new URL(requestUrl);  
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
                
                conn.setDoOutput(true);  
                conn.setDoInput(true);  
                conn.setUseCaches(false);  
                // 设置请求方式(GET/POST)  
                conn.setRequestMethod(requestMethod);  
                conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");  
                // 当outputStr不为null时向输出流写数据  
                if (null != outputStr) {  
                    OutputStream outputStream = conn.getOutputStream();  
                    // 注意编码格式  
                    outputStream.write(outputStr.getBytes("UTF-8"));  
                    outputStream.close();  
                }  
                // 从输入流读取返回内容  
                InputStream inputStream = conn.getInputStream();  
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
                String str = null;  
                StringBuffer buffer = new StringBuffer();  
                while ((str = bufferedReader.readLine()) != null) {  
                    buffer.append(str);  
                }  
                // 释放资源  
                bufferedReader.close();  
                inputStreamReader.close();  
                inputStream.close();  
                inputStream = null;  
                conn.disconnect();  
                return buffer.toString();  
            } catch (ConnectException ce) {  
            	ce.printStackTrace();
                System.out.println("连接超时:{}"+ ce);  
            } catch (Exception e) {  
                System.out.println("https请求异常:{}"+ e);  
                e.printStackTrace();
            }  
            return null;  
        }  
      //退款的请求方法  
      public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception {  
            KeyStore keyStore  = KeyStore.getInstance("PKCS12");  
            StringBuilder res = new StringBuilder("");  
            FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12"));  
            try {  
                keyStore.load(instream, "".toCharArray());  
            } finally {  
                instream.close();  
            }  
  
            // Trust own CA and all self-signed certs  
            SSLContext sslcontext = SSLContexts.custom()  
                    .loadKeyMaterial(keyStore, "1313329201".toCharArray())  
                    .build();  
            // Allow TLSv1 protocol only  
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(  
                    sslcontext,  
                    new String[] { "TLSv1" },  
                    null,  
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);  
            CloseableHttpClient httpclient = HttpClients.custom()  
                    .setSSLSocketFactory(sslsf)  
                    .build();  
            try {  
  
                HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");  
                httpost.addHeader("Connection", "keep-alive");  
                httpost.addHeader("Accept", "*/*");  
                httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");  
                httpost.addHeader("Host", "api.mch.weixin.qq.com");  
                httpost.addHeader("X-Requested-With", "XMLHttpRequest");  
                httpost.addHeader("Cache-Control", "max-age=0");  
                httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");  
                 StringEntity entity2 = new StringEntity(outputStr ,Consts.UTF_8);  
                 httpost.setEntity(entity2);  
                System.out.println("executing request" + httpost.getRequestLine());  
  
                CloseableHttpResponse response = httpclient.execute(httpost);  
                 
                try {  
                    HttpEntity entity = response.getEntity();  
                      
                    System.out.println("----------------------------------------");  
                    System.out.println(response.getStatusLine());  
                    if (entity != null) {  
                        System.out.println("Response content length: " + entity.getContentLength());  
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));  
                        String text;
                        //res.append(text);  
                        while ((text = bufferedReader.readLine()) != null) {  
                            res.append(text);  
                            System.out.println(text);  
                        }  
                         
                    }  
                    EntityUtils.consume(entity);  
                } finally {  
                    response.close();  
                }  
            } finally {  
                httpclient.close();  
            }  
            return  res.toString();  
              
        }  
      //xml解析  
      public static Map<String, Object> doXMLParse(String strxml) throws JDOMException, IOException {  
            strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");  
  
            if(null == strxml || "".equals(strxml)) {  
                return null;  
            }  
              
            Map map = 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 = getChildrenText(children);  
                }  
                  
                map.put(k, v);  
            }  
              
            //关闭流  
            in.close();  
              
            return map;  
        }  
       
}

/**
 * @ClassName: Signature
 * @Description: 微信支付签名工具类
 * @author luhanlin
 * @time 2017-12-01
 */
public class Signature {
	
	/**
     * 签名算法
     * 
     * @param o 要参与签名的数据对象
     * @return 签名
     * @throws IllegalAccessException
     */
    public static String getSign(Object o,String api_key) throws IllegalAccessException{
        ArrayList<String> list = new ArrayList<String>();
        Class cls = o.getClass();
        Field[] fields = cls.getDeclaredFields();
        for (Field f : fields){
            f.setAccessible(true);
            if (f.get(o) != null && f.get(o) != ""){
                list.add(f.getName() + "=" + f.get(o) + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++){
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + api_key;
        result = MD5Utils.MD5(result).toUpperCase();
        return result;
    }


    /**
     * 签名算法1
     * 
     * @param map 要参与签名的数据对象
     * @return 签名
     * @throws IllegalAccessException
     */
    public static String getSign(Map<String, Object> map,String api_key){
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()){
            if ( !"".equals(entry.getValue())){
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++){
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + api_key;
        System.out.println("签名:"+result);
        result = MD5Utils.MD5(result).toUpperCase();
        return result;
    }
    
    /**
     * 签名算法2
     * @param api_key
     * @param parameters 要参与签名的数据对象
     * @return
     */
    public static String createSign(String api_key,SortedMap<String,Object> parameters){
		StringBuffer sb = new StringBuffer();
		Set es = parameters.entrySet();
		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=" + api_key);
		System.out.println("签名:"+sb.toString());
		String sign =MD5Utils.MD5(sb.toString()).toUpperCase();
		
		return sign;
}

    /**
     * 签名算法3 JSAPI调用签名计算
     * 
     * @param map 要参与签名的数据对象
     * @return 签名
     * @throws IllegalAccessException
     */
    public static String getSign(Map<String, Object> map){
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()){
            if ( !"".equals(entry.getValue())){
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++){
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        //result += "key=" + WeChatConfig.API_KEY;
        result = result.substring(0, result.length()-1);
        System.out.println("签名:"+result);
        result = Sha1Util.getSha1(result);
        return result;
    }
    

    /**
     * 从API返回的XML数据里面重新计算一次签名
     * 
     * @param responseString API返回的XML数据
     * @return 新鲜出炉的签名
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static String getSignFromResponseString(String responseString,String api_key) throws Exception
    {
        Map<String, Object> map = PayCommonUtil.doXMLParse(responseString);
        // 清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
        map.put("sign", "");
        // 将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
        return Signature.getSign(map,api_key);
    }


    /**
     * 检验API返回的数据里面的签名是否合法,避免数据在传输的过程中被第三方篡改
     * 
     * @param responseString API返回的XML数据字符串
     * @return API签名是否合法
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static boolean checkIsSignValidFromResponseString(String responseString,String api_key) throws Exception,
            SAXException
    {
        Map<String, Object> map = PayCommonUtil.doXMLParse(responseString);
        String signFromAPIResponse = map.get("sign").toString();
        if (signFromAPIResponse == "" || signFromAPIResponse == null)
        {
            return false;
        }
        // 清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
        map.put("sign", "");
        // 将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
        String signForAPIResponse = Signature.createSign(api_key,new TreeMap<String, Object>(map));


        if (!signForAPIResponse.equals(signFromAPIResponse)){
            // 签名验不过,表示这个API返回的数据有可能已经被篡改了
            return false;
        }
        return true;
    }
    
    /**
     * 检验API返回的数据里面的签名是否合法,避免数据在传输的过程中被第三方篡改
     * 
     * @param responseString API返回的XML数据字符串
     * @return API签名是否合法
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static boolean checkIsSigntureValidFromResponseString(JoinUpVo vo) throws Exception,
    SAXException
    {
    	String signtureFromAPIResponse = vo.getSignature();
    	System.out.println("微信发送签名"+signtureFromAPIResponse);
    	if (signtureFromAPIResponse == "" || signtureFromAPIResponse == null)
    	{
    		return false;
    	}
    	
    	//本地生成签名进行比对
    	ArrayList<String> list = new ArrayList<String>();
    	list.add(vo.getTimestamp());
    	list.add(vo.getNonce());
    	list.add(vo.getToken());
    	
    	int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++){
            sb.append(arrayToSort[i]);
        }
        String signForAPIResponse = sb.toString();
        signForAPIResponse = Sha1Util.encode(signForAPIResponse);
    	System.out.println("本地生成签名"+signForAPIResponse);

    	if (!signForAPIResponse.equals(signtureFromAPIResponse)){
    		// 签名验不过,表示这个API返回的数据有可能已经被篡改了
    		return false;
    	}
    	return true;
    }
}

写到这里就完啦,希望不足之处大家批评指出,欢迎大佬们指点迷津 ^_^

猜你喜欢

转载自blog.csdn.net/allen_lu_hh/article/details/78774993