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