@Override
@Transactional(readOnly = false,rollbackFor = Exception.class)
public MessageBean refundOrder(HttpServletRequest request,Long id) {
MessageBean result = new MessageBean();
try {
OrderAftersalesDetailsDO orderAftersales = orderAftersalesMapper.getOne(id);
if (!orderAftersales.getStatus().equals(ConstantNumber.INTEGER_ONE)){
throw new BDException("该售后订单流程异常");
}
Object Object = redisUtil.get("refundParam" + orderAftersales.getOrderCode());
if (Object !=null){
throw new BDException("该退款订单已申请退款,请等待微信处理");
}
Order order = orderMapper.getOne(orderAftersales.getOrderId());
//订单支付金额
String total_fee = order.getPayPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString();
//退款金额
String refund_fee = orderAftersales.getRefundPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString();
//创建hashmap(用户获得签名)
SortedMap<String, String> paraMap = new TreeMap<>();
//设置请求参数(小程序ID)
paraMap.put("appid", Constant.WX_LOGIN_APPID);
//设置请求参数(商户号)
paraMap.put("mch_id", Constant.MCH_ID);
//设置请求参数(随机字符串)
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
//设置请求参数(商户订单号)
paraMap.put("out_trade_no", order.getOrderCode());
//设置请求参数(商户退款单号)
paraMap.put("out_refund_no", orderAftersales.getOrderCode());
//设置请求参数(订单金额)
// paraMap.put("total_fee", total_fee);
paraMap.put("total_fee", "1");
//设置请求参数(退款金额)
paraMap.put("refund_fee", "1");
//设置请求参数(回调地址)
paraMap.put("notify_url", "你自己的退款成功后回调地址");
//调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序)
String stringA = formatUrlMap(paraMap, false, false);
//第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。(签名)
String sign = MD5Util.MD5(stringA+"&key="+Constant.PATERNER_KEY).toUpperCase();
//将参数 编写XML格式
StringBuffer paramBuffer = new StringBuffer();
paramBuffer.append("<xml>");
paramBuffer.append("<appid>"+Constant.WX_LOGIN_APPID+"</appid>");
paramBuffer.append("<mch_id>"+Constant.MCH_ID+"</mch_id>");
paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>");
paramBuffer.append("<sign>"+sign+"</sign>");
paramBuffer.append("<out_refund_no>"+paraMap.get("out_refund_no")+"</out_refund_no>");
paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>");
paramBuffer.append("<refund_fee>"+paraMap.get("refund_fee")+"</refund_fee>");
paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>");
paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>");
paramBuffer.append("</xml>");
try {
//发送请求(POST)(获得数据包ID)(这有个注意的地方 如果不转码成ISO8859-1则会告诉你body不是UTF8编码 就算你改成UTF8编码也一样不好使 所以修改成ISO8859-1)
Map<String,String> map = WXPayUtil.xmlToMap(doRefund(request,Constant.WXPAY_REFUND, new String(paramBuffer.toString().getBytes(), "ISO8859-1")));
//应该创建 退款表数据
if (map != null && (StringUtils.isNotBlank(map.get("return_code")) && "SUCCESS".equals(map.get("return_code")))) {
result.setData(map.get("return_code"));
result.setDesc(map.get("return_msg"));
redisUtil.set("refundParam"+orderAftersales.getOrderCode(),1);
} else {
result.setData(map.get("return_code"));
result.setDesc(map.get("return_msg"));
result.setSuccess(false);
throw new BDException("退款失败,message:"+map.get("return_msg"));
}
} catch (UnsupportedEncodingException e) {
logger.info("微信 退款 异常:" + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
logger.info("微信 退款 异常:" + e.getMessage());
e.printStackTrace();
}
logger.info("微信 退款 成功");
} catch (Exception e) {
logger.error(ErrorInfoUtil.getErrorInfo(e));
throw ExceptionFormatUtil.formatException(e, ErrorEnum.SYSTEM_ERROR);
}
return result;
}
回调接口
@Override
@Transactional(readOnly = false,rollbackFor = Exception.class)
public void refundCallback(HttpServletRequest request, HttpServletResponse response) {
logger.info("退款 微信回调接口方法 start");
String inputLine = "";
String notityXml = "";
try {
while((inputLine = request.getReader().readLine()) != null){
notityXml += inputLine;
}
//关闭流
request.getReader().close();
logger.info("退款 微信回调内容信息:"+notityXml);
//解析成Map
Map<String,String> map = WXPayUtil.xmlToMap(notityXml);
//判断 退款是否成功
if("SUCCESS".equals(map.get("return_code"))){
logger.info("退款 微信回调返回是否退款成功:是");
//获得 返回的商户订单号
String passMap = AESUtil.decryptData(map.get("req_info"));
//拿到解密信息
map = WXPayUtil.xmlToMap(passMap);
//拿到解密后的订单号
String outTradeNo = map.get("out_trade_no");
//商家退款订单号
String outRefundNo = map.get("out_refund_no");
//修改售后单状态
OrderAftersales orderAftersales = orderAftersalesMapper.getByOrderCode(outRefundNo);
if (orderAftersales.getStatus().equals(ConstantNumber.INTEGER_ONE)) {
orderAftersales.setStatus(ConstantNumber.INTEGER_FOUR);
orderAftersales.setResponseTime(new Date());
orderAftersalesMapper.update(orderAftersales);
//修改订单状态
Order one = orderMapper.getOne(orderAftersales.getOrderId());
if (orderAftersales.getType().equals(Constant.INTEGER_ZERO)) {
one.setStatus(ConstantNumber.INTEGER_FIVE);
one.setRefundStatus(ConstantNumber.INTEGER_TWO);
orderMapper.update(one);
//修改库存
List<OrderCommodity> orderCommodities = orderCommodityMapper.getByOrderId(one.getId());
if (one.getOrderType()==0) {
orderCommodities.forEach(x -> {
CommoditySpecs commoditySpecs = commoditySpecsMapper.selectByPrimaryKey(x.getSpecId());
commoditySpecs.setStock(commoditySpecs.getStock() + x.getReturnQuantity());
commoditySpecsMapper.update(commoditySpecs);
x.setRefundStatus(ConstantNumber.INTEGER_TWO);
x.setReturnQuantity(x.getNum());
orderCommodityMapper.update(x);
});
}else {
orderCommodities.forEach(x -> {
SeckillCommoditySpecs commoditySpecs = seckillCommoditySpecsMapper.selectByPrimaryKey(x.getSpecId());
commoditySpecs.setStock(commoditySpecs.getStock() + x.getReturnQuantity());
seckillCommoditySpecsMapper.update(commoditySpecs);
CommoditySpecs commoditySpecs1 = commoditySpecsMapper.selectByPrimaryKey(commoditySpecs.getCommoditySpecsId());
commoditySpecs1.setStock(commoditySpecs1.getStock() + x.getReturnQuantity());
commoditySpecsMapper.update(commoditySpecs1);
x.setRefundStatus(ConstantNumber.INTEGER_TWO);
x.setReturnQuantity(x.getNum());
orderCommodityMapper.update(x);
});
}
} else {
OrderCommodity orderCommodity = orderCommodityMapper.selectByPrimaryKey(orderAftersales.getOrderCommodity());
orderCommodity.setRefundStatus(ConstantNumber.INTEGER_TWO);
orderCommodity.setReturnQuantity(orderCommodity.getReturnQuantity() + orderAftersales.getReturnQuantity());
orderCommodityMapper.update(orderCommodity);
if (one.getOrderType()==0) {
//修改库存
CommoditySpecs commoditySpecs = commoditySpecsMapper.selectByPrimaryKey(orderCommodity.getSpecId());
commoditySpecs.setStock(commoditySpecs.getStock() + orderCommodity.getReturnQuantity());
commoditySpecsMapper.update(commoditySpecs);
}else {
SeckillCommoditySpecs commoditySpecs = seckillCommoditySpecsMapper.selectByPrimaryKey(orderCommodity.getSpecId());
commoditySpecs.setStock(commoditySpecs.getStock() + orderCommodity.getReturnQuantity());
seckillCommoditySpecsMapper.update(commoditySpecs);
CommoditySpecs commoditySpecs1 = commoditySpecsMapper.selectByPrimaryKey(commoditySpecs.getCommoditySpecsId());
commoditySpecs1.setStock(commoditySpecs1.getStock() + orderCommodity.getReturnQuantity());
commoditySpecsMapper.update(commoditySpecs1);
orderCommodity.setRefundStatus(ConstantNumber.INTEGER_TWO);
orderCommodity.setReturnQuantity(orderCommodity.getNum());
orderCommodityMapper.update(orderCommodity);
}
}
}
logger.info("退款 微信回调返回商户订单号:"+map.get("out_refund_no"));
//支付成功 修改订单状态 通知微信成功回调
redisUtil.del("refundParam" + outRefundNo);
}else {
//获得 返回的商户订单号
String passMap = AESUtil.decryptData(map.get("req_info"));
//拿到解密信息
map = WXPayUtil.xmlToMap(passMap);
//拿到解密后的订单号
String outTradeNo = map.get("out_trade_no");
//更改 状态为取消
}
response.setContentType("text/xml");
//给微信服务器返回 成功标示 否则会一直询问 咱们服务器 是否回调成功
//封装 返回值
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<xml>");
stringBuilder.append("<return_code><![CDATA[SUCCESS]]></return_code>");
stringBuilder.append("<return_msg><![CDATA[OK]]></return_msg>");
stringBuilder.append("</xml>");
response.getWriter().write(stringBuilder.toString());
}catch (IOException e){
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* 方法用途: 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序),并且生成url参数串<br>
* 实现步骤: <br>
*
* @param paraMap 要排序的Map对象
* @param urlEncode 是否需要URLENCODE
* @param keyToLower 是否需要将Key转换为全小写
* true:key转化成小写,false:不转化
* @return
*/
private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){
String buff = "";
Map<String, String> tmpMap = paraMap;
try
{
List<Map.Entry<String, String>> infoIds = new ArrayList<>(tmpMap.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>()
{
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2)
{
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
// 构造URL 键值对的格式
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, String> item : infoIds)
{
if (StringUtils.isNotBlank(item.getKey()))
{
String key = item.getKey();
String val = item.getValue();
if (urlEncode)
{
val = URLEncoder.encode(val, "utf-8");
}
if (keyToLower)
{
buf.append(key.toLowerCase() + "=" + val);
} else
{
buf.append(key + "=" + val);
}
buf.append("&");
}
}
buff = buf.toString();
if (buff.isEmpty() == false)
{
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e)
{
return null;
}
return buff;
}
MD5Util
package com.guangyi.project.utils;
import java.security.MessageDigest;
public class MD5Util {
/**
* 十六进制下数字到字符的映射数组
*/
private final static String[] hexDigits = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
/**
* @Title: encodeByMD5
* @Description: 对字符串进行MD5编码
* @author yihj
* @param @param originString
* @param @return 参数
* @return String 返回类型
* @throws
*/
public static String MD5(String originString){
if (originString!=null) {
try {
//创建具有指定算法名称的信息摘要
MessageDigest md5 = MessageDigest.getInstance("MD5");
//使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
byte[] results = md5.digest(originString.getBytes());
//将得到的字节数组变成字符串返回
String result = byteArrayToHexString(results);
return result;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
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;
}
/**
* @Title: byteArrayToHexString
* @Description: 轮换字节数组为十六进制字符串
* @author yihj
* @param @param b
* @param @return 参数
* @return String 返回类型
* @throws
*/
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();
}
/**
* @Title: byteToHexString
* @Description: 将一个字节转化成十六进制形式的字符串
* @author yihj
* @param @param b
* @param @return 参数
* @return String 返回类型
* @throws
*/
private static String byteToHexString(byte b){
int n = b;
if(n<0)
n=256+n;
int d1 = n/16;
int d2 = n%16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* MD5加密 byte 数据
*
* @param source
* 要加密字符串的byte数据
* @return
*/
public static String getMD5(byte[] source) {
String s = null;
char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f' };
try {
java.security.MessageDigest md = java.security.MessageDigest
.getInstance("MD5");
md.update(source);
byte tmp[] = md.digest(); // MD5 的计算结果是一个 128 位的长整数,
// 用字节表示就是 16 个字节
char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符,
// 所以表示成 16 进制需要 32 个字符
int k = 0; // 表示转换结果中对应的字符位置
for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节
// 转换成 16 进制字符的转换
byte byte0 = tmp[i]; // 取第 i 个字节
str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换,
// >>>
// 为逻辑右移,将符号位一起右移
str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
}
s = new String(str); // 换后的结果转换为字符串
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
}
private String doRefund(HttpServletRequest request,String url,String data) throws Exception{
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// String substring = request.getSession().getServletContext().getRealPath("/").substring(0, request.getSession().getServletContext().getRealPath("/").lastIndexOf("webapp\\"));
FileInputStream instream = new FileInputStream(FileUtils.getResourcesPath()+"refund_certificate/apiclient_cert.p12");//P12文件目录 证书路径,文件在文章下面
try {
/**
* 此处要改
* */
keyStore.load(instream, Constant.MCH_ID.toCharArray());//这里写密码..默认是你的MCHID
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
/**
* 此处要改
* */
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, Constant.MCH_ID.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(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.guangyi.project.utils;
import com.guangyi.project.config.Constant;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESUtil {
/**
* 密钥算法
*/
private static final String ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
/**
* 生成key
*/
private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(Constant.PATERNER_KEY, "UTF-8").toLowerCase().getBytes(), ALGORITHM);
/**
* AES加密
*
* @param data
* @return
* @throws Exception
*/
public static String encryptData(String data) throws Exception {
// 创建密码器
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64Util.encode(cipher.doFinal(data.getBytes()));
}
/**
* AES解密
*
* @param base64Data
* @return
* @throws Exception
*/
public static String decryptData(String base64Data) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64Utils.decode(base64Data)));
}
}
其他的微信Utils在上一章有
链接:点击下载PKCS12证书
提取码:0121