2
0

feat: 退款审批

This commit is contained in:
caiyuchao
2025-03-31 10:25:14 +08:00
parent 1a65c76613
commit 5f46f3044b
36 changed files with 942 additions and 88 deletions

View File

@@ -37,6 +37,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.wfc.common.core.domain.R;
import org.wfc.common.security.annotation.InnerAuth;
import org.wfc.payment.domain.AliPayBean;
import org.wfc.payment.domain.vo.AjaxResult;
import org.wfc.payment.pay.alipay.service.IAliPayService;
@@ -384,10 +386,11 @@ public class AliPayController extends AbstractAliPayApiController {
/**
* 退款
*/
@InnerAuth
@PostMapping(value = "/tradeRefund")
public String tradeRefund(@RequestParam Long orderId) {
public R<Boolean> tradeRefund(@RequestParam Long orderId) {
return aliPayService.tradeRefund(orderId);
return R.ok(aliPayService.tradeRefund(orderId));
}
/**

View File

@@ -16,5 +16,5 @@ public interface IAliPayService {
String notifyUrl(HttpServletRequest request, String publicKey);
String tradeRefund(Long orderId);
Boolean tradeRefund(Long orderId);
}

View File

@@ -7,10 +7,12 @@ import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.ijpay.alipay.AliPayApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.wfc.common.core.domain.R;
import org.wfc.common.core.enums.PaymentTypeEnum;
import org.wfc.payment.pay.alipay.service.IAliPayService;
import org.wfc.user.api.RemoteUUserService;
import org.wfc.user.api.domain.vo.UOrderVo;
@@ -105,7 +107,7 @@ public class AliPayServiceImpl implements IAliPayService {
if (verifyResult) {
// 请在这里加上商户的业务逻辑程序代码 异步通知可能出现订单重复通知 需要做去重处理
String outTradeNo = params.get("out_trade_no");
remoteUUserService.paySuccess(Long.valueOf(outTradeNo), "inner");
remoteUUserService.paySuccess(PaymentTypeEnum.ALIPAY.getCode(), Long.valueOf(outTradeNo), "inner");
return "success";
} else {
log.error("notify_url 验证失败");
@@ -118,7 +120,7 @@ public class AliPayServiceImpl implements IAliPayService {
}
@Override
public String tradeRefund(Long orderId) {
public Boolean tradeRefund(Long orderId) {
try {
String totalAmount = getAmountByOrder(orderId);
if (StrUtil.isBlank(totalAmount)) {
@@ -131,10 +133,13 @@ public class AliPayServiceImpl implements IAliPayService {
}
model.setRefundAmount(totalAmount);
model.setRefundReason("normal refund");
return AliPayApi.tradeRefundToResponse(model).getBody();
AlipayTradeRefundResponse alipayTradeRefundResponse = AliPayApi.tradeRefundToResponse(model);
if ("Y".equals(alipayTradeRefundResponse.getFundChange())) {
return true;
}
} catch (AlipayApiException e) {
log.error("alipay refund error", e);
}
return null;
return false;
}
}

View File

@@ -19,7 +19,6 @@ import com.ijpay.wxpay.model.MicroPayModel;
import com.ijpay.wxpay.model.OrderQueryModel;
import com.ijpay.wxpay.model.ProfitSharingModel;
import com.ijpay.wxpay.model.ReceiverModel;
import com.ijpay.wxpay.model.RefundModel;
import com.ijpay.wxpay.model.RefundQueryModel;
import com.ijpay.wxpay.model.SendRedPackModel;
import com.ijpay.wxpay.model.TransferModel;
@@ -29,11 +28,14 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.wfc.common.core.domain.R;
import org.wfc.common.security.annotation.InnerAuth;
import org.wfc.payment.domain.H5SceneInfo;
import org.wfc.payment.domain.WxPayBean;
import org.wfc.payment.domain.vo.AjaxResult;
@@ -766,37 +768,11 @@ public class WxPayController extends AbstractWxPayApiController {
/**
* 微信退款
*/
@RequestMapping(value = "/refund", method = {RequestMethod.POST, RequestMethod.GET})
@InnerAuth
@PostMapping(value = "/refund")
@ResponseBody
public String refund(@RequestParam(value = "transactionId", required = false) String transactionId,
@RequestParam(value = "outTradeNo", required = false) String outTradeNo) {
try {
log.info("transactionId: {} outTradeNo:{}", transactionId, outTradeNo);
if (StrUtil.isBlank(outTradeNo) && StrUtil.isBlank(transactionId)) {
return "transactionId、out_trade_no二选一";
}
WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
Map<String, String> params = RefundModel.builder()
.appid(wxPayApiConfig.getAppId())
.mch_id(wxPayApiConfig.getMchId())
.nonce_str(WxPayKit.generateStr())
.transaction_id(transactionId)
.out_trade_no(outTradeNo)
.out_refund_no(WxPayKit.generateStr())
.total_fee("1")
.refund_fee("1")
.notify_url(refundNotifyUrl)
.build()
.createSign(wxPayApiConfig.getPartnerKey(), SignType.MD5);
String refundStr = WxPayApi.orderRefund(false, params, wxPayApiConfig.getCertPath(), wxPayApiConfig.getMchId());
log.info("refundStr: {}", refundStr);
return refundStr;
} catch (Exception e) {
e.printStackTrace();
}
return null;
public R<String> refund(@RequestParam(value = "orderId") Long orderId) {
return R.ok(wxPayService.refund(orderId, refundNotifyUrl));
}
/**
@@ -831,24 +807,7 @@ public class WxPayController extends AbstractWxPayApiController {
@RequestMapping(value = "/refundNotify", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public String refundNotify(HttpServletRequest request) {
String xmlMsg = HttpKit.readData(request);
log.info("退款通知=" + xmlMsg);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
String returnCode = params.get("return_code");
// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
if (WxPayKit.codeIsOk(returnCode)) {
String reqInfo = params.get("req_info");
String decryptData = WxPayKit.decryptData(reqInfo, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey());
log.info("退款通知解密后的数据=" + decryptData);
// 更新订单信息
// 发送通知等
Map<String, String> xml = new HashMap<String, String>(2);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}
return null;
return wxPayService.refundNotify(request);
}
@RequestMapping(value = "/sendRedPack", method = {RequestMethod.POST, RequestMethod.GET})

View File

@@ -12,4 +12,8 @@ public interface IWxPayService {
String scanCode2(HttpServletRequest request, Long orderId, String notifyUrl);
String payNotify(HttpServletRequest request);
String refund(Long orderId, String notifyUrl);
String refundNotify(HttpServletRequest request);
}

View File

@@ -2,6 +2,8 @@ package org.wfc.payment.pay.wxpay.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.ijpay.core.enums.SignType;
@@ -13,11 +15,14 @@ import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.WxPayApiConfig;
import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.model.RefundModel;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.wfc.common.core.domain.R;
import org.wfc.common.core.enums.PaymentTypeEnum;
import org.wfc.common.core.exception.ServiceException;
import org.wfc.payment.pay.wxpay.service.IWxPayService;
import org.wfc.payment.utils.MultipartFileUtil;
@@ -51,16 +56,7 @@ public class WxPayServiceImpl implements IWxPayService {
@Override
public String scanCode2(HttpServletRequest request, Long orderId, String notifyUrl) {
R<UOrderVo> orderRes = remoteUUserService.getOrderById(orderId);
UOrderVo orderVo = orderRes.getData();
if (orderVo == null) {
throw new ServiceException("payment.pay.error");
}
String totalFee = orderVo.getOrderAmount().multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).toString();
if (StrUtil.isBlank(totalFee)) {
throw new ServiceException("payment.pay.error");
}
String totalFee = getAmountByOrder(orderId);
String ip = IpKit.getRealIp(request);
if (StrUtil.isBlank(ip)) {
@@ -140,7 +136,7 @@ public class WxPayServiceImpl implements IWxPayService {
// 发送通知等
Map<String, String> xml = new HashMap<String, String>(2);
String outTradeNo = params.get("out_trade_no");
remoteUUserService.paySuccess(Long.valueOf(outTradeNo), "inner");
remoteUUserService.paySuccess(PaymentTypeEnum.WX_PAY.getCode(), Long.valueOf(outTradeNo), "inner");
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
@@ -148,4 +144,76 @@ public class WxPayServiceImpl implements IWxPayService {
}
return null;
}
@Override
public String refund(Long orderId, String notifyUrl) {
try {
String totalFee = getAmountByOrder(orderId);
log.info("outTradeNo:{}", orderId);
WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
Map<String, String> params = RefundModel.builder()
.appid(wxPayApiConfig.getAppId())
.mch_id(wxPayApiConfig.getMchId())
.nonce_str(WxPayKit.generateStr())
.out_trade_no(orderId.toString())
.out_refund_no(WxPayKit.generateStr())
.total_fee(totalFee)
.refund_fee(totalFee)
.notify_url(notifyUrl)
.build()
.createSign(wxPayApiConfig.getPartnerKey(), SignType.MD5);
String refundStr = WxPayApi.orderRefund(false, params, wxPayApiConfig.getCertPath(), wxPayApiConfig.getMchId());
log.info("refundStr: {}", refundStr);
return refundStr;
} catch (Exception e) {
log.error("refund error", e);
}
return null;
}
@Override
public String refundNotify(HttpServletRequest request) {
String xmlMsg = HttpKit.readData(request);
log.info("退款通知=" + xmlMsg);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
String returnCode = params.get("return_code");
// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
if (WxPayKit.codeIsOk(returnCode)) {
String reqInfo = params.get("req_info");
String decryptData = WxPayKit.decryptData(reqInfo, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey());
log.info("退款通知解密后的数据=" + decryptData);
JSONObject dataJson = JSONUtil.parseObj(decryptData);
String refundStatus = dataJson.get("refund_status").toString();
if ("SUCCESS".equals(refundStatus)) {
String outTradeNo = dataJson.get("out_trade_no").toString();
remoteUUserService.refundSuccess(PaymentTypeEnum.WX_PAY.getCode(), Long.valueOf(outTradeNo), "inner");
// 更新订单信息
// 发送通知等
Map<String, String> xml = new HashMap<String, String>(2);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}
}
return null;
}
private @NotNull String getAmountByOrder(Long orderId) {
R<UOrderVo> orderRes = remoteUUserService.getOrderById(orderId);
UOrderVo orderVo = orderRes.getData();
if (orderVo == null) {
throw new ServiceException("payment.pay.error");
}
String totalFee = orderVo.getOrderAmount().multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).toString();
if (StrUtil.isBlank(totalFee)) {
throw new ServiceException("payment.pay.error");
}
return totalFee;
}
}