以前一直没有接触支付这一块,突然接到要做公众号的支付着实让我头疼了几天,下面就说一说微信公众号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("timeStamp", PayUtil.createTimeStamp());map.put("appId", "");
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; } }
写到这里就完啦,希望不足之处大家批评指出,欢迎大佬们指点迷津 ^_^