mall + pay:

1. 优化 PayClient 退款逻辑,返回业务失败 errorCode + errorMsg 错误码
This commit is contained in:
YunaiV 2023-07-18 13:00:22 +08:00
parent 6f475f8c85
commit 1c282bd3cb
4 changed files with 130 additions and 90 deletions

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import lombok.Data;
@ -44,4 +45,71 @@ public class PayRefundRespDTO {
*/
private Object rawData;
/**
* 调用渠道的错误码
*
* 注意这里返回的是业务异常而是不系统异常
* 如果是系统异常则会抛出 {@link PayException}
*/
private String channelErrorCode;
/**
* 调用渠道报错时错误信息
*/
private String channelErrorMsg;
private PayRefundRespDTO() {
}
/**
* 创建WAITING状态的退款返回
*/
public static PayRefundRespDTO waitingOf(String channelRefundNo,
String outRefundNo, Object rawData) {
PayRefundRespDTO respDTO = new PayRefundRespDTO();
respDTO.status = PayRefundStatusRespEnum.WAITING.getStatus();
respDTO.channelRefundNo = channelRefundNo;
// 相对通用的字段
respDTO.outRefundNo = outRefundNo;
respDTO.rawData = rawData;
return respDTO;
}
/**
* 创建SUCCESS状态的退款返回
*/
public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime,
String outRefundNo, Object rawData) {
PayRefundRespDTO respDTO = new PayRefundRespDTO();
respDTO.status = PayRefundStatusRespEnum.SUCCESS.getStatus();
respDTO.channelRefundNo = channelRefundNo;
respDTO.successTime = successTime;
// 相对通用的字段
respDTO.outRefundNo = outRefundNo;
respDTO.rawData = rawData;
return respDTO;
}
/**
* 创建FAILURE状态的退款返回
*/
public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) {
return failureOf(null, null,
outRefundNo, rawData);
}
/**
* 创建FAILURE状态的退款返回
*/
public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg,
String outRefundNo, Object rawData) {
PayRefundRespDTO respDTO = new PayRefundRespDTO();
respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus();
respDTO.channelErrorCode = channelErrorCode;
respDTO.channelErrorMsg = channelErrorMsg;
// 相对通用的字段
respDTO.outRefundNo = outRefundNo;
respDTO.rawData = rawData;
return respDTO;
}
}

View File

@ -98,7 +98,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
try {
return doParseOrderNotify(params, body);
} catch (Throwable ex) {
log.error("[parseOrderNotify][params({}) body({}) 解析失败]", params, body, ex);
log.error("[parseOrderNotify][客户端({}) params({}) body({}) 解析失败]",
getId(), params, body, ex);
throw buildPayException(ex);
}
}
@ -129,6 +130,20 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
@Override
public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
try {
return doParseRefundNotify(params, body);
} catch (Throwable ex) {
log.error("[parseRefundNotify][客户端({}) params({}) body({}) 解析失败]",
getId(), params, body, ex);
throw buildPayException(ex);
}
}
protected abstract PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body)
throws Throwable;
// ========== 各种工具方法 ==========
private PayException buildPayException(Throwable ex) {

View File

@ -29,7 +29,6 @@ import java.util.Objects;
import java.util.function.Supplier;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* 支付宝抽象类实现支付宝统一的接口以及部分实现退款
@ -94,7 +93,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
* @return 退款请求 Response
*/
@Override
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 构建 AlipayTradeRefundModel 请求
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(reqDTO.getOutTradeNo());
@ -104,31 +103,22 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
// 1.2 构建 AlipayTradePayRequest 请求
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizModel(model);
try {
// 2.1 执行请求
AlipayTradeRefundResponse response = client.execute(request);
// 2.2 创建返回结果
PayRefundRespDTO refund = new PayRefundRespDTO()
.setOutRefundNo(reqDTO.getOutRefundNo())
.setRawData(response);
// 支付宝只要退款调用返回 success就认为退款成功不需要回调具体可见 parseNotify 方法的说明
// 另外支付宝没有退款单号所以不用设置
if (response.isSuccess()) {
refund.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
.setSuccessTime(LocalDateTimeUtil.of(response.getGmtRefundPay()));
Assert.notNull(refund.getSuccessTime(), "退款成功时间不能为空");
} else {
refund.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus());
}
return refund;
} catch (AlipayApiException e) {
log.error("[doUnifiedRefund][request({}) 发起退款异常]", toJsonString(reqDTO), e);
return null;
// 2.1 执行请求
AlipayTradeRefundResponse response = client.execute(request);
// 2.2 创建返回结果
// 支付宝只要退款调用返回 success就认为退款成功不需要回调具体可见 parseNotify 方法的说明
// 另外支付宝没有退款单号所以不用设置
if (response.isSuccess()) {
return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()),
reqDTO.getOutRefundNo(), response);
} else {
return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
}
}
@Override
public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
// 补充说明支付宝退款时没有回调这点和微信支付是不同的并且退款分成部分退款和全部退款
// 部分退款是会有回调但是它回调的是订单状态的同步回调不是退款订单的回调
// 全部退款Wap 支付有订单状态的同步回调但是 PC/扫码又没有

View File

@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
@ -163,9 +162,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
// todo 芋艿异常的处理
// throw buildUnifiedOrderException(null, e);
return null;
String errorCode = getErrorCode(e);
String errorMessage = getErrorMessage(e);
return PayRefundRespDTO.failureOf(errorCode, errorMessage,
reqDTO.getOutTradeNo(), e.getXmlString());
}
}
@ -181,17 +181,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
// 2.1 执行请求
WxPayRefundResult response = client.refundV2(request);
// 2.2 创建返回结果
PayRefundRespDTO refund = new PayRefundRespDTO()
.setOutRefundNo(reqDTO.getOutRefundNo())
.setRawData(response);
if (Objects.equals("SUCCESS", response.getResultCode())) {
refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
.setChannelRefundNo(response.getRefundId());
} else {
refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
return PayRefundRespDTO.waitingOf(response.getRefundId(),
reqDTO.getOutRefundNo(), response);
}
// TODO 芋艿异常的处理
return refund;
return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
}
private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
@ -206,78 +200,51 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
// 2.1 执行请求
WxPayRefundV3Result response = client.refundV3(request);
// 2.2 创建返回结果
PayRefundRespDTO refund = new PayRefundRespDTO()
.setOutRefundNo(reqDTO.getOutRefundNo())
.setRawData(response);
if (Objects.equals("SUCCESS", response.getStatus())) {
refund.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
.setChannelRefundNo(response.getRefundId())
.setSuccessTime(parseDateV3(response.getSuccessTime()));
} else if (Objects.equals("PROCESSING", response.getStatus())) {
refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
.setChannelRefundNo(response.getRefundId());
} else {
refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
reqDTO.getOutRefundNo(), response);
}
// TODO 芋艿异常的处理
return refund;
if (Objects.equals("PROCESSING", response.getStatus())) {
return PayRefundRespDTO.waitingOf(response.getRefundId(),
reqDTO.getOutRefundNo(), response);
}
return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
}
@Override
public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
try {
// 微信支付 v2 回调结果处理
switch (config.getApiVersion()) {
case API_VERSION_V2:
return parseRefundNotifyV2(body);
case WxPayClientConfig.API_VERSION_V3:
return parseRefundNotifyV3(body);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
throw new RuntimeException(e);
// TODO 芋艿缺一个异常翻译
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws WxPayException {
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doParseRefundNotifyV2(body);
case WxPayClientConfig.API_VERSION_V3:
return parseRefundNotifyV3(body);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
}
@SuppressWarnings("DuplicatedCode")
private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException {
// 1. 解析回调
WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo();
// 2. 构建结果
PayRefundRespDTO notify = new PayRefundRespDTO()
.setChannelRefundNo(responseResult.getRefundId())
.setOutRefundNo(responseResult.getOutRefundNo())
.setRawData(response);
if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
.setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
} else {
notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
if (Objects.equals("SUCCESS", result.getRefundStatus())) {
return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()),
result.getOutRefundNo(), response);
}
return notify;
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
}
@SuppressWarnings("DuplicatedCode")
private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
// 1. 解析回调
WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
// 2. 构建结果
PayRefundRespDTO notify = new PayRefundRespDTO()
.setChannelRefundNo(responseResult.getRefundId())
.setOutRefundNo(responseResult.getOutRefundNo())
.setRawData(response);
if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
.setSuccessTime(parseDateV3(responseResult.getSuccessTime()));
} else {
notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
if (Objects.equals("SUCCESS", result.getRefundStatus())) {
return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()),
result.getOutRefundNo(), response);
}
return notify;
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
}
// ========== 各种工具方法 ==========