接上一篇微信支付
微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->证书下载
编写代码
/**
* 微信退款
* @throws Exception
*/
public JsonReturn refundFunction(HttpServletRequest req,HttpServletResponse resp,Map<String, Object> map,Refuse refuse) throws Exception {
String appid = WeixinUtil.appid;
String mch_id = WeixinUtil.mch_id;
String mch_key = WeixinUtil.mch_key;
//jsapi接口初始化
RequestHandler requestHandler=new RequestHandler(req, resp);
XMLHelper xh;
requestHandler.init();
requestHandler.init(appid, mch_key);
//这里的map主要有两个参数(商户号mchId和订单金额totalFee)
String result = "";//这里用于返回处理返回结果
//获取商户订单号和订单金额
String mchId = map.get("mchId").toString();
//获取订单金额(退款金额默认全部)
String a = map.get("totalFee")+"";
String b = Double.valueOf(a)*100 + "";
int lastindex = b.indexOf(".");
b = b.substring(0 , lastindex);
int c = Integer.parseInt(b);
//随机字符串
String noncestr = Sha1Util.getNonceStr();
//退款单号
String refuseNo = map.get("refuseNo").toString();
// String d = c + "";
// System.out.println("refundMoney--------->"+refundMoney);
String totalFee = c + "";
//回调地址,换成自己的
String notify_url="www.sss.com";
SortedMap<String, String> refund = new TreeMap<String, String>();
refund.put("appid", appid);//公众账号ID
refund.put("mch_id", mch_id);//商户号
refund.put("nonce_str",noncestr);//随机字符串
refund.put("out_trade_no",mchId);//商户订单号
refund.put("out_refund_no",refuseNo);//商户退款订单号
refund.put("total_fee",totalFee);//订单金额
refund.put("refund_desc","主动退款");//退款原因
refund.put("refund_fee",totalFee);//退款金额
refund.put("notify_url", notify_url);//微信回调通知地址,可以使用自己定义也可以使用回调到指定的位置这里使用的是自定义可以在回调中添加一些功能
String sign = requestHandler.createSign(refund);
refund.put("sign",sign);//签名
//xml和map之间的转换
String reuqestXml = WeixinUtil.getRequestXml(refund);
String doRefund = PayUtil.doRefund(mch_id, "https://api.mch.weixin.qq.com/secapi/pay/refund", reuqestXml);
Map<String, Object> xmlMap = PayUtil.doXMLParse(doRefund);
JsonReturn addRefuse = refuseManageService.addRefuse(refuse);
//状态码校验格式是否正确,Success是正确的
if(xmlMap.get("return_code").equals("SUCCESS")){
//退款的结果这是一个map
// System.out.println(result);
// String xmlResult= PaymentKit.toXml(result);//map转xml
// System.out.println(xmlResult);
if("SUCCESS".equals(xmlMap.get("result_code"))){
Map<String, Object> packageParams = new HashMap<String, Object>();
packageParams.put("result_code",xmlMap.get("result_code"));//业务返回结果
packageParams.put("appid", xmlMap.get("appid"));//公众账号ID
packageParams.put("mch_id",xmlMap.get("mch_id"));//商户号
packageParams.put("nonce_str",xmlMap.get("nonce_str"));//随机字符串
packageParams.put("sign",xmlMap.get("sign"));//签名
packageParams.put("transaction_id",xmlMap.get("transaction_id"));//微信订单号
packageParams.put("out_trade_no",xmlMap.get("out_trade_no"));//商户订单号
packageParams.put("out_refund_no",xmlMap.get("out_refund_no"));//商户退款订单号
packageParams.put("refund_id",xmlMap.get("refund_id"));//微信退款订单号
packageParams.put("refund_fee",xmlMap.get("refund_fee"));//退款金额
packageParams.put("total_fee",xmlMap.get("total_fee"));//标价金额
packageParams.put("cash_fee",xmlMap.get("cash_fee"));//现金支付金额
//退款申请成功
System.out.println("退款成功");
//TODO
//回调写好需要写在回调接口里
//修改退款状态为成功
//refuseManageService.updRefuseStatus(mchId,"1");
//修改订单状态为已退款
refuseManageService.updOrderStatusByOrderNo(mchId, "16");
}else if("FAIL".equals(xmlMap.get("result_code"))){
//失败
String err_code_des = (String) xmlMap.get("err_code_des");//返回信息
System.out.println(err_code_des);
//修改退款状态为失败
refuseManageService.updRefuseStatus(mchId,"2",err_code_des);
return new JsonReturn(JsonReturn.CODE_ERROR,err_code_des);
}
}else if("FAIL".equals(xmlMap.get("return_code"))){
//失败
String err_code_des = (String) xmlMap.get("err_code_des");//返回信息
System.out.println(err_code_des);
//修改退款状态为失败
refuseManageService.updRefuseStatus(mchId,"2",err_code_des);
return new JsonReturn(JsonReturn.CODE_ERROR,err_code_des);
}
refuseManageService.updRefuseStatus(mchId,"1","退款成功");
return addRefuse;
}
微信退款回调
/**
* 退款结果通知
* <p>
* 在申请退款接口中上传参数“notify_url”以开通该功能
* 如果链接无法访问,商户将无法接收到微信通知。
* 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
* <p>
* 当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。
* 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。
* (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
* 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
* 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
* 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容
* @param request req
* @param response resp
* @return res xml
*
*/
@RequestMapping(value="/callback.ohtml",method = RequestMethod.POST)
public void refund(HttpServletRequest request, HttpServletResponse response) {
String resXml = "";
InputStream inStream;
try {
inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
System.out.println("refund:微信退款----start----");
// 获取微信调用我们notify_url的返回信息
String result = new String(outSteam.toByteArray(), "utf-8");
System.out.println("refund:微信退款----result----=" + result);
// 关闭流
outSteam.close();
inStream.close();
Map<String, String> map = PayUtil.doXMLParse(result);
boolean isSuccess = false;
if("SUCCESS".equalsIgnoreCase(map.get("return_code"))) {
System.out.println("refund:微信退款----返回成功");
/** 以下字段在return_code为SUCCESS的时候有返回: **/
// 加密信息:加密信息请用商户秘钥进行解密,详见解密方式
String req_info = map.get("req_info");
System.out.println("req_info:"+req_info);
/**
* 解密方式
* 解密步骤如下:
* (1)对加密串A做base64解码,得到加密串B
* (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
* (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
*/
String resultStr = AESUtil.decryptData(req_info);
// WXPayUtil.getLogger().info("refund:解密后的字符串:" + resultStr);
Map<String, String> aesMap = PayUtil.doXMLParse(resultStr);
/** 以下为返回的加密字段: **/
// 商户退款单号 是 String(64) 1.21775E+27 商户退款单号
String out_refund_no = aesMap.get("out_refund_no");
// 退款状态 是 String(16) SUCCESS SUCCESS-退款成功、CHANGE-退款异常、REFUNDCLOSE—退款关闭
String refund_status = aesMap.get("refund_status");
// 商户订单号 是 String(32) 1.21775E+27 商户系统内部的订单号
String out_trade_no = aesMap.get("out_trade_no");
// 微信订单号 是 String(32) 1.21775E+27 微信订单号
String transaction_id = null;
// 微信退款单号 是 String(32) 1.21775E+27 微信退款单号
String refund_id = null;
// 订单金额 是 Int 100 订单总金额,单位为分,只能为整数,详见支付金额
String total_fee = null;
// 应结订单金额 否 Int 100 当该订单有使用非充值券时,返回此字段。应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。
String settlement_total_fee = null;
// 申请退款金额 是 Int 100 退款总金额,单位为分
String refund_fee = null;
// 退款金额 是 Int 100 退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额
String settlement_refund_fee = null;
// 退款是否成功
if (!"SUCCESS".equals(refund_status)) {
resXml = resFailXml;
} else {
// 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = resSuccessXml;
isSuccess = true;
}
//修改退款状态为成功
Integer updRefuseStatus = refuseManageService.updRefuseStatus(out_trade_no,"1","退款成功");
if (updRefuseStatus > 0) {
//修改订单状态为已退款
Integer updOrderStatusByOrderNo = refuseManageService.updOrderStatusByOrderNo(out_trade_no, "16");
// 处理业务 - 修改订单状态
System.out.println("refund:微信支付回调:修改的订单===>" + out_refund_no);
if (updOrderStatusByOrderNo > 0) {
System.out.println("refund:微信支付回调:修改订单支付状态成功");
} else {
System.out.println("refund:微信支付回调:修改订单支付状态失败");
}
}
} else {
System.out.println("refund:支付失败,错误信息:" + map.get("return_msg"));
resXml = resFailXml;
}
} catch (Exception e) {
System.out.println("refund:微信退款回调发布异常:"+e);
} finally {
try {
// 处理业务完毕
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} catch (IOException e) {
System.out.println("refund:微信退款回调发布异常:out:"+ e);
}
}
}
下面是所用到的工具类
Sha1Util
package com.hhz.basic.util.weixin;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
/*
'============================================================================
'api说明:
'createSHA1Sign创建签名SHA1
'getSha1()Sha1签名
'============================================================================
'*/
public class Sha1Util {
public static String getNonceStr() {
Random random = new Random();
return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8");
}
public static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
public static String getMchBillBo(String mchId) {
return mchId+String.valueOf(System.currentTimeMillis());
}
//创建签名SHA1
public static String createSHA1Sign(SortedMap<String, String> signParams) throws Exception {
StringBuffer sb = new StringBuffer();
Set es = signParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
sb.append(k + "=" + v + "&");
//要采用URLENCODER的原始值!
}
String params = sb.substring(0, sb.lastIndexOf("&"));
System.out.println("sha1 sb:" + params);
return getSha1(params);
}
//Sha1签名
public static String getSha1(String str) {
if (str == null || str.length() == 0) {
return null;
}
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f' };
try {
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));
byte[] md = mdTemp.digest();
int j = md.length;
char buf[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
buf[k++] = hexDigits[byte0 & 0xf];
}
return new String(buf);
} catch (Exception e) {
return null;
}
}
}
WeiXinUtil
package com.vue.utils.weixin;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONObject;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.hhz.basic.util.common.HttpRequestHelper;
import com.hhz.basic.util.common.JacksonHelper;
import com.hhz.basic.util.common.JsonReturn;
import com.hhz.basic.util.common.JsonUtils;
import com.hhz.basic.util.common.PropertiesHelper;
import com.hhz.basic.util.common.UnicodeUtil;
import com.hhz.basic.util.common.WebUtils;
import com.hhz.basic.util.redis.RedisUtil;
import com.hhz.basic.util.weixin.HttpRequester;
import com.hhz.basic.util.weixin.HttpResponser;
public class WeixinUtil implements Serializable{
private static final long serialVersionUID = 1L;
/**
* ACCESS_TOKEN的过期时间 微信是7200秒 这里每6000秒刷新一次token 不用每次都请求微信接口获取数据
*/
public int EXPIRES_IN=6000;
public static String RANDOM_REDIS_ACCESS_TOKEN="GZH_RANDOM_REDIS_ACCESS_TOKEN";
/**
* JSAPI_TICKET的过期时间是7200秒 这里每6000秒刷新一次JSAPI_TICKET 不用每次都请求微信接口获取数据
*/
public int EXPIRES_IN_TICK=6000;
public static String RANDOM_REDIS_JSAPI_TICKET="GZH_RANDOM_REDIS_JSAPI_TICKET";
// public WeixinUtil(String orId,String id,String pass,String mId,String mKey) {
// super();
// appid=id;
// secret=pass;
// mchId=mId;
// mchKey=mKey;
// originalId=orId;
// }
public WeixinUtil() {
}
// private String originalId = "";
// private String appid = "";
// private String secret = "";
// private String mchId = "";
// private String mchKey = "";
public static String originalId = PropertiesHelper.getProperty("originalId", "config.properties");
public static String appid = PropertiesHelper.getProperty("appId", "config.properties");
public static String secret = PropertiesHelper.getProperty("secret", "config.properties");
public static String mch_id = PropertiesHelper.getProperty("mchId", "config.properties");
public static String mch_key = PropertiesHelper.getProperty("mchKey", "config.properties");
/**
* 获取 AccessToken
* @return
* @throws UnsupportedEncodingException
*/
public String getAccessToken() throws Exception {
System.out.println(StringUtils.isBlank(RedisUtil.getStringValue(RANDOM_REDIS_ACCESS_TOKEN+appid)));
if(StringUtils.isBlank(RedisUtil.getStringValue(RANDOM_REDIS_ACCESS_TOKEN+appid))){
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+secret;
String json = HttpRequestHelper.getData(url, "UTF-8");
Map<String, Object> map = JacksonHelper.fromJSON(json, HashMap.class);
String accessToken = map != null && map.get("access_token") != null ? map.get("access_token").toString() : "";
if(accessToken != null && !"".equals(accessToken)){
RedisUtil.setExpireString(RANDOM_REDIS_ACCESS_TOKEN+appid,EXPIRES_IN,accessToken);
}else{
System.out.println(this.appid+"----"+json);
Map<String,Object> mapError = new HashMap<String, Object>();
mapError.put("appId", this.appid);
mapError.put("json", json);
mapError.put("erroroper", "获取公众号信息");
RedisUtil.setQueueValue(PropertiesHelper.getProperty("cmb_error_code", "config.properties"),mapError);
}
return accessToken;
}else{
return RedisUtil.getStringValue(RANDOM_REDIS_ACCESS_TOKEN+appid);
}
}
/**
* 调用js-sdk所需的配置参数
* @param jsapi_ticket
* @param url
* @return
*/
public Map<String, String> sign(String jsapi_ticket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
ret.put("url", url);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
return ret;
}
public String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
public String create_nonce_str() {
return UUID.randomUUID().toString();
}
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
/**
* 调用接口之前判断accessToken
*/
public Map<String, Object> estimateAccessToken(){
Map<String, Object> map = null;
String accessToken = null;
try {
accessToken = getAccessToken();
if(accessToken == null){
map.put("errorMsg", "获取accessToken错误,请重试");
return map;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
map.put("errorMsg", "获取accessToken错误,请重试");
return map;
}
return null;
}
/**
* 获取Jsapi_ticket
* @return
* @throws Exception
*/
public String getJsapi_ticket() throws Exception{
if(RedisUtil.getStringValue(RANDOM_REDIS_JSAPI_TICKET+appid)==null){
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+getAccessToken()+"&type=jsapi";
HttpRequester req = new HttpRequester();
String myjson = "";
try {
HttpResponser res = req.sendGet(url);
myjson = res.getContent();
validateAccessToken(myjson);
} catch (Exception e) {
e.printStackTrace();
}
HashMap jo = JacksonHelper.fromJSON(myjson, HashMap.class);
String ticket = (String) jo.get("ticket");
RedisUtil.setExpireString(RANDOM_REDIS_JSAPI_TICKET+appid,EXPIRES_IN_TICK,ticket);
return ticket;
}else{
return RedisUtil.getStringValue(RANDOM_REDIS_JSAPI_TICKET+appid);
}
}
/**
* 调用js-sdk所需的配置参数
* @param url
* @return
*/
public Map getJsapiMap(HttpServletRequest request) {
//微信分享逻辑
String url=WebUtils.getServerPath(request)+request.getServletPath();
if(request.getQueryString()!=null){
url += "?"+request.getQueryString();//完整url 加上参数
}
Map<String, String> result = new HashMap<String,String>();
try {
String jsapi_ticket = getJsapi_ticket();
Map<String, String> ret = sign(jsapi_ticket, url);
for (Map.Entry entry : ret.entrySet()) {
result.put(entry.getKey().toString(), entry.getValue().toString());
}
} catch (Exception e) {
System.out.println("获取微信服务号API错误(本地开发忽略此错误)");
e.printStackTrace();
}
result.put("appId", appid);
return result;
}
//如果token无效清空token
public void validateAccessToken(String json){
if(json==null||(json!=null&&json.contains("errcode")&&json.contains("40001"))||(json!=null&&json.contains("errcode")&&json.contains("42001"))){
RedisUtil.removeString(RANDOM_REDIS_ACCESS_TOKEN+appid);
RedisUtil.removeString(RANDOM_REDIS_JSAPI_TICKET+appid);
}
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public Map<String,Object> getInfoTemplate() throws Exception{
String sendurl = "https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token="+getAccessToken();
String myjson = HttpRequestHelper.getData(sendurl,"UTF-8");
// System.out.println("myjson-line353:"+myjson);
validateAccessToken(myjson);
Map<String,Object> map=JacksonHelper.fromJSON(myjson, HashMap.class);
return map;
}
public String mediaUploadVideo(String type,String path,String title,String introduction){
try {
PostMethod filePost = new PostMethod();
String result=postFile("https://api.weixin.qq.com/cgi-bin/material/add_material?access_token="+getAccessToken()+"&type="+type, path,title,introduction);
System.out.println(path+"----"+result+"//");
HashMap jo = JacksonHelper.fromJSON(result, HashMap.class);
if(jo != null && jo.get("errcode") != null){
Map<String,Object> mapError= new HashMap<String, Object>();
mapError.put("appid", this.appid);
mapError.put("json",result);
mapError.put("erroroper", "上传永久素材");
RedisUtil.setQueueValue(PropertiesHelper.getProperty("cmb_error_code", "config.properties"),mapError);
}
validateAccessToken(result.toString());
String media_id = (String) jo.get("media_id");
return media_id;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* 转为xml格式
* @param parameters
* @return
*/
public static String getRequestXml(SortedMap<String,String> 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 k = (String)entry.getKey();
String v = (String)entry.getValue();
if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
}
PayUtil
package com.vue.utils.weixin;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
public class PayUtil {
/**
* 签名字符串
* @param //text需要签名的字符串
* @param key 密钥
* @param //input_charset编码格式
* @return 签名结果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
/**
* 签名字符串
* @param //text需要签名的字符串
* @param sign 签名结果
* @param //key密钥
* @param input_charset 编码格式
* @return 签名结果
*/
public static boolean verify(String text, String sign, String key, String input_charset) {
text = text + key;
String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
if (mysign.equals(sign)) {
return true;
} else {
return false;
}
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* @param content
* @param charset
* @return
* @throws SignatureException
* @throws UnsupportedEncodingException
*/
public static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
private static boolean isValidChar(char ch) {
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
return true;
if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))
return true;// 简体中文汉字编码
return false;
}
/**
* 除去数组中的空值和签名参数
* @param sArray 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
*
* @param //requestUrl请求地址
* @param //requestMethod请求方法
* @param //outputStr参数
*/
public static String httpRequest(String requestUrl,String requestMethod,String outputStr){
// 创建SSLContext
StringBuffer buffer = null;
try{
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//往服务器端写内容
if(null !=outputStr){
OutputStream os=conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 读取服务器端返回的内容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
br.close();
}catch(Exception e){
e.printStackTrace();
}
return buffer.toString();
}
public static String urlEncodeUTF8(String source){
String result=source;
try {
result=java.net.URLEncoder.encode(source, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws Exception {
if(null == strxml || "".equals(strxml)) {
return null;
}
/*============= !!!!注意,修复了微信官方反馈的漏洞,更新于2018-10-16 ===========*/
try {
Map<String, String> data = new HashMap<String, String>();
// TODO 在这里更换
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
//documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
InputStream stream = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilderFactory.newDocumentBuilder().parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
throw ex;
}
}
/**
* 获取子结点的xml
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
public static String doRefund(String mchId,String url, String data) throws Exception{
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
String filepath = "D:/apiclient_cert.p12";
//1、得到数据文件
File file = new File(filepath);
System.out.println("filepath->"+filepath);
FileInputStream instream = new FileInputStream(file);
try {
keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mchId.toCharArray())//这里也是写密码的
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpost = new HttpPost(url); // 设置响应头信息
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) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}
AESUtil
package com.vue.utils.weixin;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import sun.misc.BASE64Decoder;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.hhz.basic.util.common.Base64Utils;
/**
* 微信支付AES加解密工具类
*
* @author yclimb
* @date 2018/6/21
*/
public class AESUtil {
/**
* 密钥算法
*/
private static final String ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
/**
* 生成key
*/
private static SecretKeySpec KEY;
static {
try {
KEY = new SecretKeySpec(PayUtil.MD5(WeixinUtil.mch_key).toLowerCase().getBytes(), ALGORITHM);
// System.out.println("KEY:"+KEY);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* AES加密
*
* @param data d
* @return str
* @throws Exception e
*/
public static String encryptData(String data) throws Exception {
// 创建密码器
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, KEY);
return Base64Utils.encode(new String(cipher.doFinal(data.getBytes()), "ISO-8859-1"));
}
/**
* AES解密
*
* @param base64Data 64
* @return str
* @throws Exception e
*/
public static String decryptData(String base64Data) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
cipher.init(Cipher.DECRYPT_MODE, KEY);
return new String(cipher.doFinal(base64Decode8859(base64Data).getBytes("ISO-8859-1")), "utf-8");
}
/**
* Base64解码
* @param source base64 str
* @return str
*/
@SuppressWarnings("restriction")
public static String base64Decode8859(final String source) {
String result = "";
//BASE64解码
final BASE64Decoder decoder = new BASE64Decoder();
try {
byte[] b = decoder.decodeBuffer(source);
// 此处的字符集是ISO-8859-1
result = new String(b, "ISO-8859-1");
} catch (final Exception e) {
e.printStackTrace();
}
return result;
}
/**
* Base64加密
* @param source str
* @return base64 str
*/
@SuppressWarnings("restriction")
public static String base64Encode8859(final String source) {
String result = "";
byte[] textByte = null;
try {
//注意此处的编码是ISO-8859-1
textByte = source.getBytes("ISO-8859-1");
result = new sun.misc.BASE64Encoder().encode(textByte);
} catch (final UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) throws Exception {
// String A = "qS/pmvAXYetUObwHm9bAod9G3SVBKQK5CiIgETwHJT4ExUpJnIg87m37KlokIsBZCnQBIO2Ear7Q/IazZ6jDNsnmsITqYt1hPYloGjdjRGlqdSSBVRjk9NIkRRQIlb+5AOHJttfVKMsbMK8FzoysE+rL8yKaOzXvsNCA2g60z3bEw3x891ZwPPiUSkVJGeIHpafWdR94Y/j3hfsrEw5KOTGiPneH5d9zhC73MW/kDWu9+wDkJCtCf5fNc9GIC5x2zKNZozpQ9wT/WLyjSz/En166xbgUt9tApaaQSayFQ0eSokMjYYLKO5KJQ355QtkvZlW96rX9IO6hVHXDgPD7kJOTh/L99ZQtG5umLBfOd9i3xVH4qH+gvi/i0gEpvQOhTvxcrZeKs8Rsliua46u/aBdUy6GlICRxQPmvKBfL9cE2L5MZGqHkCMTmSr1i4L8Ubxoi3Yv6TCTTOo4MVc64igb9HttMVfOiLFrZKAyH64Y5C6+GATUMSzWhXDn089QyrZk+W6GFkQlA6dBlO7v0aucF8t3L6SFtnxm6XkH6eD4/FFxKz+wsqKDX1s+GnPGQdwjxsS3RLGjJuNoSB7N+v4AUbMgLT2sBzew89ow7/vEUMjJMQt3eISwprOaDZqZQBgdLVUwDyWnrWi50Rr2wEuJXv/m6x8f40wN93L8GvGbMsWGXlp9V9W3LR2LZD9CnrWAlhoYoDGMAwCKuPh+dfjXmVGttGxegM+PlUR8nq6Qr1zwHz4dV3PgzWlf3n5qR72tAJ/Y0045n3dT7Iw4UNzBHC6XkUIA884paHbZ3D0V95+WrdyVQ4icsgZIneaAMZfslVsnigUjnXl3m/qZGlW5A6d93VXNe8bQgA6s6lJeEsaZc3sLVPi5Tlr2nfbgdhB4XqYkR4DebEbUzalSOqM+OOeCsYj920+FboIxvShy2ECk6bjebMM3kYw0s1NUWXynKFTvbgZ35H9TNKaeom1qYVmbb/581N8+sO3yDDFzZaaqLqOtaUtgIe2SOS2A6GRnKSanqbsJVU4j2amWEpicl3WchYV9KPeuoqodu+4UCsaY2juUIcbbof/ygkG5NkDz27RA4fBxxAlqvtzEftw==";
Security.addProvider(new BouncyCastleProvider());
System.out.println(AESUtil.decryptData(A));
/*String B = AESUtil.decryptData(A);
System.out.println(B);*/
}
}
Base64Utils
package com.hhz.basic.util.common;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.bouncycastle.util.encoders.Base64;
/**
* <p>
* BASE64编码解码工具包
* </p>
* <p>
* 依赖javabase64-1.3.1.jar
* </p>
*
* @date 2012-5-19
* @version 1.0
*/
public class Base64Utils {
/**
* 文件读取缓冲区大小
*/
private static final int CACHE_SIZE = 1024;
/**
* <p>
* BASE64 解码
* </p>
*
* @param base64
* @return
* @throws Exception
*/
public static String decode(String base64) {
return new String(Base64.decode(base64.getBytes()));
}
/**
* <p>
* BASE64 解码
* </p>
*
* @param base64
* @return
* @throws
*/
public static String decodeUtf8(String base64) {
try {
return new String(Base64.decode(base64.getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
/**
* <p>
* BASE64 编码
* </p>
*
* @param bytes
* @return
* @throws Exception
*/
public static String encode(String base64) {
return new String(Base64.encode(base64.getBytes()));
}
/**
* <p>
* 文件转换为二进制数组
* </p>
*
* @param filePath 文件路径
* @return
* @throws Exception
*/
public static byte[] fileToByte(String filePath) throws Exception {
byte[] data = new byte[0];
File file = new File(filePath);
if (file.exists()) {
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
out.write(cache, 0, nRead);
out.flush();
}
out.close();
in.close();
data = out.toByteArray();
}
return data;
}
/**
* <p>
* 二进制数据写文件
* </p>
*
* @param bytes 二进制数据
* @param filePath 文件生成目录
*/
public static void byteArrayToFile(byte[] bytes, String filePath) throws Exception {
InputStream in = new ByteArrayInputStream(bytes);
File destFile = new File(filePath);
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
destFile.createNewFile();
OutputStream out = new FileOutputStream(destFile);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
out.write(cache, 0, nRead);
out.flush();
}
out.close();
in.close();
}
}
注意:我们使用AES加密时候就出现 Illegal key size 错误,查阅资料知道了如果密钥大于128, 会抛出异常。因为密钥长度是受限制的, java运行时环境读到的是受限的policy文件,文件位于/jre/lib/security下, 这种限制是因为某国对软件出口的控制。
将下面链接中的jar包下载下来,替换jdk 与jre下两个jar包:local_policy.jar和US_export_policy.jar即可。
JDK6: http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
JDK7: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
JDK8: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
jdk对应jar包的路径(找自己的路径):C:\Java\jdk1.7.0_67\jre\lib\security
jre对应jar包的路径(找自己的路径):C:\Java\jre7\lib\security