微信退款
@ApiOperation(value = "确认并退款", notes = "确认并退款")
@PutMapping(value = "/updateAfter/{refundId}")
@ApiImplicitParams({@ApiImplicitParam(name = "ACCESS_TOKEN", value = "接口调用凭证", defaultValue = "06855244f2f221da4cd0a395c0d3c68c", dataType = "string", required = true, paramType = "query")})
public void updateAfter(@ApiParam(value = "售后单Id", required = true) @PathVariable("refundId") Integer refundId,
@ApiParam(value = "订单类型(0 购物车付款 1 直接支付)")@RequestParam(value = "orderType")Integer orderType) {
AdminUser adminUser = SessionUtil.getUser(request, AdminUser.class);
goodsOrderRefundService.updateAfter(refundId,adminUser.getId(), HttpUtil.getIpAddr(request),orderType);
}
@ApiOperation(value = "微信退款回调通知", notes = "订单退款回调通知", hidden = true)
@PostMapping(value = "/notify")
@ResponseAdvice(value = ResponseAdviceEnum.NOT_REQUIRED)
public String payNotify() {
boolean checkFlag = true;
try {
String notifyInfo = RequestUtil.parseWechatNotifyParams(request);
Map<String, String> map = WechatPayUtil.checkCallBackWechatRefund(notifyInfo);
checkFlag = goodsOrderRefundService.updateNotice(map);
} catch (Exception e) {
e.printStackTrace();
}
return WechatPayUtil.generateWxCallBackResponse(checkFlag);
}
/**
* 确认收货并退款
*
* @param refundId
* @param orderType
*/
@Transactional(rollbackFor = Exception.class)
public void updateAfter(Integer refundId, Integer adminId, String ip, Integer orderType) {
GoodsOrderRefund goodsOrderRefund = new GoodsOrderRefund();
if (Objects.equals(orderType, shoppingPay.getCode())){
goodsOrderRefund = shoppingPay(refundId);
}else {
OrderRefundBo orderRefundBo = goodsOrderRefundMapper.getOrderRefundBo(refundId);
AssertUtil.notNull(orderRefundBo.getPayType(), ExceptionEnum.OrderRefundInvalid);
goodsOrderRefund.setId(refundId);
goodsOrderRefund.setDeleteFlag(DeleteFlagEnum.NotDeleted.getCode());
goodsOrderRefund = goodsOrderRefundMapper.selectOne(goodsOrderRefund);
AssertUtil.notNull(goodsOrderRefund, ExceptionEnum.invalidOrderRefund);
//生成退款单号
String refundNo = "R" + DateUtils.getTimeString(DateUtils.DATE_YYYYMMDDHHMMSS, new Date()) + RandomStringUtils.randomNumeric(6);
//更新退款单状态为-退款中
GoodsOrderRefund temp = new GoodsOrderRefund();
temp.setRefundStatus(OrderRefundStatusEnum.refunding.getCode());
temp.setRefundNo(refundNo);
Example examples = new Example(GoodsOrderRefund.class);
examples.createCriteria()
.andEqualTo("id", refundId)
.andEqualTo("deleteFlag", DeleteFlagEnum.NotDeleted.getCode());
goodsOrderRefundMapper.updateByExampleSelective(temp, examples);
//更新订单详情为-退款中
GoodsOrderDetail goodsOrderDetail = new GoodsOrderDetail();
goodsOrderDetail.setRefundStatus(OrderRefundStatusEnum.refunding.getCode());
Example goodsOrderDetailExample = new Example(GoodsOrderDetail.class);
goodsOrderDetailExample.createCriteria().andEqualTo("orderId", goodsOrderRefund.getOrderId())
.andEqualTo("goodsProductId", goodsOrderRefund.getGoodsProductId())
.andEqualTo("refundStatus", OrderRefundStatusEnum.checking.getCode());
goodsOrderDetailMapper.updateByExampleSelective(goodsOrderDetail, goodsOrderDetailExample);
WechatPayDto dto = goodsOrderRefundMapper.selectRefund(refundId);
//订单id
String outTradeNo = dto.getOutTradeNo();
//订单总金额
String totalFee = dto.getTotalFee();
//退款金额
String refundFee = dto.getRefundFee();
//退款单号
String outRefundNo = dto.getOutRefundNo();
//微信反的订单号
String transactionId = dto.getTransactionId();
/******************微信退款 ***************/
if (Objects.equals(orderRefundBo.getPayType(), PayTypeEnum.waitDelivery.getCode())) {
Map<String, String> refund = WechatPayUtil.refund(transactionId, outTradeNo, outRefundNo,
new BigDecimal(totalFee), new BigDecimal(refundFee), WechatPayUtil.REFUND_NOTIFY_URL);
//用来判断数据是否为真
System.out.println(refund);
AssertUtil.isTrue(Objects.equals(refund.get("result_code"), "SUCCESS") &&
Objects.equals(refund.get("return_code"), "SUCCESS"), ExceptionEnum.wechatRefundError);
}
/************************wechat.properties***************/
app.id=*****
app.secret=*****
mch.id=1488883392
pay.secret=*********
## 自己电脑ip
refund.notify.url=http://*****/refund/notify
##微信退款证书
cert.url=C:/Users/Administrator/Desktop/apiclient_cert.p12
package com.qcdl.web.wechat;
import com.google.common.collect.Maps;
import com.qcdl.utils.CodecUtil;
import com.qcdl.utils.HttpUtil;
import com.qcdl.utils.PropertiesUtil;
import com.qcdl.web.config.advice.ExceptionEnum;
import com.qcdl.web.config.advice.WebException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import javax.annotation.Nonnull;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* @author qcdl
* @date 2019/6/11
*/
@Slf4j
public class WechatPayUtil {
private static final PropertiesUtil WECHAT_PRO = new PropertiesUtil("/configs/wechat.properties");
public static final String REFUND_NOTIFY_URL;
private static final String APP_ID;
private static final String MCH_ID;
private static final String PAY_SECRET;
private static final String CERT_URL;
static {
APP_ID = WECHAT_PRO.getProperty("app.id");
MCH_ID = WECHAT_PRO.getProperty("mch.id");
REFUND_NOTIFY_URL = WECHAT_PRO.getProperty("refund.notify.url");
PAY_SECRET = WECHAT_PRO.getProperty("pay.secret");
CERT_URL = WECHAT_PRO.getProperty("cert.url");
}
private static final String TRADE_STATUS_SUCCESS = "SUCCESS";
private static String generateWechatPaySign(Map<String, String> params) {
String plainValue = MapUtils.mapParamOrderByAscJoinUrlDelimiter(params) + "&key=" + PAY_SECRET;
return CodecUtil.getMD5Cryptography(plainValue).toUpperCase();
}
/**
* 校验微信支付回调信息
*
* @param notifyInfo 微信支付回调信息
* @return true/false
*/
public static boolean checkCallBackWechatPaySign(String notifyInfo) throws DocumentException {
Map<String, String> params = XmlUtils.xmlStr2Map(notifyInfo);
String oldSignStr = params.get("sign");
params.remove("sign");
String newSignStr = generateWechatPaySign(params);
if (!oldSignStr.equals(newSignStr)) {
return false;
}
return StringUtils.equalsIgnoreCase(params.get("return_code"), TRADE_STATUS_SUCCESS)
&& StringUtils.equalsIgnoreCase(params.get("result_code"), TRADE_STATUS_SUCCESS);
}
/**
* 生成微信支付回调响应信息
*
* @param checkFlag 签名校验结果
* @return 微信支付回调响应信息
*/
public static String generateWxCallBackResponse(boolean checkFlag) {
Map<String, String> params = Maps.newHashMap();
String returnCode = "FAIL";
String returnMsg = "校验签名失败";
if (checkFlag) {
returnCode = "SUCCESS";
returnMsg = "OK";
}
params.put("return_code", returnCode);
params.put("return_msg", returnMsg);
return XmlUtils.map2XmlStr(params);
}
/**
* 微信订单申请退款
* 1、交易时间超过一年的订单无法提交退款
* 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。
* 申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
* 3、每个支付订单的部分退款次数不能超过50次
* <p>
* 退款金额<=订单总金额
*
* @param transactionId 微信订单ID
* @param orderNo 系统订单号
* @param refundNo 系统退款单号
* @param orderAmount 订单总金额
* @param refundAmount 退款金额
* @param notifyUrl 退款回调通知地址
* @return 微信退款
*/
public static Map<String, String> refund(String transactionId, String orderNo, String refundNo, BigDecimal orderAmount, BigDecimal refundAmount, String notifyUrl) {
try {
String refundOrder = generateRefund(transactionId, orderNo, refundNo, orderAmount, refundAmount, notifyUrl);
String refundResult = RequestUtil.httpsRequestAndCert(WechatConstant.REFUND_ORDER, refundOrder, CERT_URL, MCH_ID);
return XmlUtils.xmlStr2Map(refundResult);
} catch (Exception e) {
e.printStackTrace();
}
throw new WebException(ExceptionEnum.Exception);
}
private static String generateRefund(String transactionId, @Nonnull String orderNo, String refundNo,
BigDecimal orderAmount, @Nonnull BigDecimal refundAmount, @Nonnull String notifyUrl) {
Map<String, String> params = Maps.newHashMap();
params.put("appid", APP_ID);
params.put("mch_id", MCH_ID);
params.put("nonce_str", RandomStringUtils.randomAlphabetic(6));
params.put("transaction_id", transactionId);
params.put("out_trade_no", orderNo);
params.put("out_refund_no", refundNo);
orderAmount = orderAmount.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_DOWN);
refundAmount = refundAmount.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_DOWN);
params.put("total_fee", orderAmount.toString());
params.put("refund_fee", refundAmount.toString());
params.put("notify_url", notifyUrl);
params.put("refund_account", "REFUND_SOURCE_RECHARGE_FUNDS");
params.put("sign", generateWechatPaySign(params));
return XmlUtils.map2XmlStr(MapUtils.mapParamOrderByAsc(params));
}
public static Map<String, String> checkCallBackWechatRefund(String notifyInfo) throws Exception {
Map<String, String> params = XmlUtils.xmlStr2Map(notifyInfo);
String reqInfo = params.get("req_info");
byte[] encryptMsg = Base64.getDecoder().decode(reqInfo.getBytes(StandardCharsets.UTF_8));
String encryptKey = CodecUtil.getMD5Cryptography(PAY_SECRET).toLowerCase();
String plainNotifyInfo = AESUtil.decryptData(encryptMsg, encryptKey);
return XmlUtils.xmlStr2Map(plainNotifyInfo);
}
public static Map<String, String> queryOrderRefund(@NonNull String orderNo, @NonNull Integer page) throws DocumentException {
String refundParam = generateOrderRefundParam(orderNo, page);
String result = HttpUtil.getResult(WechatConstant.QUERY_ORDER_REFUND, refundParam);
return XmlUtils.xmlStr2Map(result);
}
public static Map<String, String> queryOrderPayStart(@NonNull String orderNo) throws DocumentException {
String queryOrderPayParam = generateQueryOrderParam(orderNo);
String result = HttpUtil.getResult(WechatConstant.QUERY_ORDER, queryOrderPayParam);
return XmlUtils.xmlStr2Map(result);
}
private static String generateQueryOrderParam(String orderNo) {
HashMap<String, String> params = Maps.newHashMap();
params.put("appid", APP_ID);
params.put("mch_id", MCH_ID);
params.put("out_trade_no", orderNo);
params.put("nonce_str", RandomStringUtils.randomAlphabetic(6));
params.put("sign", generateWechatPaySign(params));
return XmlUtils.map2XmlStr(MapUtils.mapParamOrderByAsc(params));
}
private static String generateOrderRefundParam(String orderNo, Integer page) {
Map<String, String> params = Maps.newHashMap();
params.put("appid", APP_ID);
params.put("mch_id", MCH_ID);
params.put("nonce_str", RandomStringUtils.randomAlphabetic(6));
params.put("out_trade_no", orderNo);
params.put("offset", page * 10 + "");
params.put("sign", generateWechatPaySign(params));
return XmlUtils.map2XmlStr(MapUtils.mapParamOrderByAsc(params));
}
}
/*****************RequestUtil************/
package com.qcdl.web.wechat;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayOfflineMaterialImageUploadRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
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.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.*;
/**
* @author qcdl
* @date 2019/6/10
*/
public final class RequestUtil {
/**
* 封装前台请求参数并转换为map保存
*
* @param request request
* @return map
*/
public static Map<String, String> parseRequestParam(HttpServletRequest request) {
Map<String, String> result = new HashMap<>(16);
Enumeration<String> enumerations = request.getParameterNames();
while (enumerations.hasMoreElements()) {
String key = enumerations.nextElement();
result.put(key, request.getParameter(key));
}
return result;
}
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(StandardCharsets.UTF_8));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str;
StringBuilder buffer = new StringBuilder();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解析微信回调通知参数(简单封装,仅限微信回调用)
*
* @param request request
* @return 微信回调通知参数
* @throws IOException e
*/
public static String parseWechatNotifyParams(HttpServletRequest request) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
in.close();
return sb.toString();
}
public static String getFullRequestUrl(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if (StringUtils.isNotBlank(request.getQueryString())) {
url += "?" + request.getQueryString();
}
return url;
}
public static String httpsRequestAndCert(String requestUrl, String param, String certUrl, String certPass) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream inputStream = new FileInputStream(new File(certUrl))) {
keyStore.load(inputStream, certPass.toCharArray());
}
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, certPass.toCharArray())
.build();
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslFactory)
.build();
StringBuilder stringBuffer = new StringBuilder();
try {
HttpPost httpPost = new HttpPost(requestUrl);
InputStream is = new ByteArrayInputStream(param.getBytes(StandardCharsets.UTF_8));
InputStreamEntity inputStreamEntity = new InputStreamEntity(is, is.available());
httpPost.setEntity(inputStreamEntity);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
HttpEntity entity = response.getEntity();
BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(), StandardCharsets.UTF_8));
String inputLine;
while ((inputLine = reader.readLine()) != null) {
stringBuffer.append(inputLine);
}
}
} finally {
httpClient.close();
}
return stringBuffer.toString();
}
}
@Data
@ApiModel
public class WechatPayDto {
@ApiModelProperty("微信订单号")
private String transactionId;
@ApiModelProperty("订单总金额")
private String totalFee;
@ApiModelProperty("系统支付单号")
private String outTradeNo;
@ApiModelProperty("系统退款单号")
private String outRefundNo;
@ApiModelProperty("微信退款单号")
private String refundId;
//退款资金来源 (REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款/基本账户、REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款)
@ApiModelProperty("退款资金来源")
private String refundAccount;
//退款总金额,单位为分,可以做部分退款
@ApiModelProperty("退款总金额")
private String refundFee;
@ApiModelProperty("退款入账账户")
private String refundRecvAccount;
//退款状态(SUCCESS—退款成功、REFUNDCLOSE—退款关闭、PROCESSING—退款处理中、CHANGE—退款异常)
@ApiModelProperty("退款状态")
private String refundStatus;
@ApiModelProperty("退款成功时间")
private String successTime;
}
网友评论