mall + pay:

1. 优化 PayClient 支付逻辑,返回业务失败 errorCode + errorMsg 错误码
This commit is contained in:
YunaiV 2023-07-18 07:37:03 +08:00
parent 7cf4c5415e
commit 6f475f8c85
29 changed files with 361 additions and 292 deletions

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; 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.dto.refund.PayRefundUnifiedReqDTO;
@ -30,7 +29,7 @@ public interface PayClient {
* @param reqDTO 下单信息 * @param reqDTO 下单信息
* @return 各支付渠道的返回结果 * @return 各支付渠道的返回结果
*/ */
PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
/** /**
* 解析 order 回调数据 * 解析 order 回调数据

View File

@ -1,10 +1,9 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.order; package cn.iocoder.yudao.framework.pay.core.client.dto.order;
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -14,9 +13,6 @@ import java.time.LocalDateTime;
* @author 芋道源码 * @author 芋道源码
*/ */
@Data @Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderRespDTO { public class PayOrderRespDTO {
/** /**
@ -48,8 +44,94 @@ public class PayOrderRespDTO {
private LocalDateTime successTime; private LocalDateTime successTime;
/** /**
* 原始的异步通知结果 * 原始的同步/异步通知结果
*/ */
private Object rawData; private Object rawData;
// ========== 主动发起支付时会返回的字段 ==========
/**
* 展示模式
*
* 枚举 {@link PayOrderDisplayModeEnum}
*/
private String displayMode;
/**
* 展示内容
*/
private String displayContent;
/**
* 调用渠道的错误码
*
* 注意这里返回的是业务异常而是不系统异常
* 如果是系统异常则会抛出 {@link PayException}
*/
private String channelErrorCode;
/**
* 调用渠道报错时错误信息
*/
private String channelErrorMsg;
public PayOrderRespDTO() {
}
/**
* 创建WAITING状态的订单返回
*/
public PayOrderRespDTO(String displayMode, String displayContent,
String outTradeNo, Object rawData) {
this.status = PayOrderStatusRespEnum.WAITING.getStatus();
this.displayMode = displayMode;
this.displayContent = displayContent;
// 相对通用的字段
this.outTradeNo = outTradeNo;
this.rawData = rawData;
}
/**
* 创建SUCCESS状态的订单返回
*/
public PayOrderRespDTO(String channelOrderNo, String channelUserId, LocalDateTime successTime,
String outTradeNo, Object rawData) {
this.status = PayOrderStatusRespEnum.SUCCESS.getStatus();
this.channelOrderNo = channelOrderNo;
this.channelUserId = channelUserId;
this.successTime = successTime;
// 相对通用的字段
this.outTradeNo = outTradeNo;
this.rawData = rawData;
}
/**
* 创建SUCCESSCLOSED状态的订单返回适合支付渠道回调时
*/
public PayOrderRespDTO(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime,
String outTradeNo, Object rawData) {
this.status = status;
this.channelOrderNo = channelOrderNo;
this.channelUserId = channelUserId;
this.successTime = successTime;
// 相对通用的字段
this.outTradeNo = outTradeNo;
this.rawData = rawData;
}
/**
* 创建CLOSED状态的订单返回适合调用支付渠道失败时
*
* 参数和 {@link #PayOrderRespDTO(String, String, String, Object)} 冲突所以独立个方法出来
*/
public static PayOrderRespDTO build(String channelErrorCode, String channelErrorMsg,
String outTradeNo, Object rawData) {
PayOrderRespDTO respDTO = new PayOrderRespDTO();
respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus();
respDTO.channelErrorCode = channelErrorCode;
respDTO.channelErrorMsg = channelErrorMsg;
// 相对通用的字段
respDTO.outTradeNo = outTradeNo;
respDTO.rawData = rawData;
return respDTO;
}
} }

View File

@ -43,7 +43,6 @@ public class PayOrderUnifiedReqDTO {
/** /**
* 商品描述信息 * 商品描述信息
*/ */
@NotEmpty(message = "商品描述信息不能为空")
@Length(max = 128, message = "商品描述信息长度不能超过128") @Length(max = 128, message = "商品描述信息长度不能超过128")
private String body; private String body;
/** /**

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import lombok.Data;
/**
* 统一下单 Response DTO
*
* @author 芋道源码
*/
@Data
public class PayOrderUnifiedRespDTO {
/**
* 展示模式
*
* 枚举 {@link PayOrderDisplayModeEnum}
*/
private String displayMode;
/**
* 展示内容
*/
private String displayContent;
/**
* 渠道支付订单
*
* 只有在订单直接支付成功时才会进行返回
* 目前只有 bar 条码支付才会出现它是支付发起时直接返回是否支付成功的而其它支付还是异步通知
*/
private PayOrderRespDTO order;
public PayOrderUnifiedRespDTO(String displayMode, String displayContent) {
this.displayMode = displayMode;
this.displayContent = displayContent;
}
}

View File

@ -1,16 +1,17 @@
package cn.iocoder.yudao.framework.pay.core.client.impl; package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; 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.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.validation.Validation; import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@ -29,6 +30,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
/** /**
* 渠道编码 * 渠道编码
*/ */
@SuppressWarnings("FieldCanBeLocal")
private final String channelCode; private final String channelCode;
/** /**
* 支付配置 * 支付配置
@ -73,31 +75,42 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
// ============ 支付相关 ========== // ============ 支付相关 ==========
@Override @Override
public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public final PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); ValidationUtils.validate(reqDTO);
// 执行统一下单 // 执行统一下单
PayOrderUnifiedRespDTO resp; PayOrderRespDTO resp;
try { try {
resp = doUnifiedOrder(reqDTO); resp = doUnifiedOrder(reqDTO);
} catch (ServiceException ex) {
// 业务异常都是实现类已经翻译所以直接抛出即可
throw ex;
} catch (Throwable ex) { } catch (Throwable ex) {
// 系统异常则包装成 PayException 异常抛出 // 系统异常则包装成 PayException 异常抛出
log.error("[unifiedRefund][request({}) 发起支付异常]", toJsonString(reqDTO), ex); log.error("[unifiedRefund][客户端({}) request({}) 发起支付异常]",
throw buildException(ex); getId(), toJsonString(reqDTO), ex);
throw buildPayException(ex);
} }
return resp; return resp;
} }
protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) protected abstract PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
throws Throwable;
@Override
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
try {
return doParseOrderNotify(params, body);
} catch (Throwable ex) {
log.error("[parseOrderNotify][params({}) body({}) 解析失败]", params, body, ex);
throw buildPayException(ex);
}
}
protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body)
throws Throwable; throws Throwable;
// ============ 退款相关 ========== // ============ 退款相关 ==========
@Override @Override
public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); ValidationUtils.validate(reqDTO);
// 执行统一退款 // 执行统一退款
PayRefundRespDTO resp; PayRefundRespDTO resp;
try { try {
@ -107,8 +120,9 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
throw ex; throw ex;
} catch (Throwable ex) { } catch (Throwable ex) {
// 系统异常则包装成 PayException 异常抛出 // 系统异常则包装成 PayException 异常抛出
log.error("[unifiedRefund][request({}) 发起退款异常]", toJsonString(reqDTO), ex); log.error("[unifiedRefund][客户端({}) request({}) 发起退款异常]",
throw buildException(ex); getId(), toJsonString(reqDTO), ex);
throw buildPayException(ex);
} }
return resp; return resp;
} }
@ -117,7 +131,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========
private PayException buildException(Throwable ex) { private PayException buildPayException(Throwable ex) {
if (ex instanceof PayException) { if (ex instanceof PayException) {
return (PayException) ex; return (PayException) ex;
} }

View File

@ -5,8 +5,8 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; 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.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
@ -29,9 +29,7 @@ import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.ORDER_UNIFIED_ERROR;
/** /**
* 支付宝抽象类实现支付宝统一的接口以及部分实现退款 * 支付宝抽象类实现支付宝统一的接口以及部分实现退款
@ -55,6 +53,40 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
this.client = new DefaultAlipayClient(alipayConfig); this.client = new DefaultAlipayClient(alipayConfig);
} }
// ============ 支付相关 ==========
/**
* 构造支付关闭的 {@link PayOrderRespDTO} 对象
*
* @return 支付关闭的 {@link PayOrderRespDTO} 对象
*/
protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) {
Assert.isFalse(response.isSuccess());
return PayOrderRespDTO.build(response.getSubCode(), response.getSubMsg(),
reqDTO.getOutTradeNo(), response);
}
@Override
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws Throwable {
// 1. 校验回调数据
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
StandardCharsets.UTF_8.name(), config.getSignType());
// 2. 解析订单的状态
String tradeStatus = bodyObj.get("trade_status");
Integer status = Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus()
: Objects.equals("TRADE_SUCCESS", tradeStatus) ? PayOrderStatusRespEnum.SUCCESS.getStatus()
: Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null;
Assert.notNull(status, (Supplier<Throwable>) () -> {
throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
});
return new PayOrderRespDTO(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")),
bodyObj.get("out_trade_no"), body);
}
// ============ 退款相关 ==========
/** /**
* 支付宝统一的退款接口 alipay.trade.refund * 支付宝统一的退款接口 alipay.trade.refund
* *
@ -95,32 +127,6 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
} }
} }
@Override
@SneakyThrows
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
// 1. 校验回调数据
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
StandardCharsets.UTF_8.name(), config.getSignType());
// 2. 解析订单的状态
String tradeStatus = bodyObj.get("trade_status");
PayOrderStatusRespEnum status = Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING
: Objects.equals("TRADE_SUCCESS", tradeStatus) ? PayOrderStatusRespEnum.SUCCESS
: Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED : null;
Assert.notNull(status, (Supplier<Throwable>) () -> {
throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
});
return PayOrderRespDTO.builder()
.status(Objects.requireNonNull(status).getStatus())
.outTradeNo(bodyObj.get("out_trade_no"))
.channelOrderNo(bodyObj.get("trade_no"))
.channelUserId(bodyObj.get("seller_id"))
.successTime(parseTime(params.get("gmt_payment")))
.rawData(body)
.build();
}
@Override @Override
public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) { public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
// 补充说明支付宝退款时没有回调这点和微信支付是不同的并且退款分成部分退款和全部退款 // 补充说明支付宝退款时没有回调这点和微信支付是不同的并且退款分成部分退款和全部退款
@ -145,21 +151,4 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER); return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER);
} }
/**
* 校验支付宝统一下单的响应
*
* 如果校验不通过则抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 异常
*
* @param request 请求
* @param response 响应
*/
protected void validateUnifiedOrderResponse(Object request, AlipayResponse response) {
if (response.isSuccess()) {
return;
}
log.error("[validateUnifiedOrderResponse][发起支付失败request({})response({})]",
JsonUtils.toJsonString(request), JsonUtils.toJsonString(response));
throw exception0(ORDER_UNIFIED_ERROR.getCode(), response.getSubMsg());
}
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
@ -27,7 +27,7 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
} }
@Override @Override
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 构建 AlipayTradeAppPayModel 请求 // 1.1 构建 AlipayTradeAppPayModel 请求
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
// 通用的参数 // 通用的参数
@ -49,8 +49,11 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
// 2.1 执行请求 // 2.1 执行请求
AlipayTradeAppPayResponse response = client.execute(request); AlipayTradeAppPayResponse response = client.execute(request);
// 2.2 处理结果 // 2.2 处理结果
validateUnifiedOrderResponse(request, response); if (!response.isSuccess()) {
return new PayOrderUnifiedRespDTO(displayMode, ""); return buildClosedPayOrderRespDTO(reqDTO, response);
}
return new PayOrderRespDTO(displayMode, "",
reqDTO.getOutTradeNo(), response);
} }
} }

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
@ -30,7 +30,7 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
} }
@Override @Override
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code"); String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code");
if (StrUtil.isEmpty(authCode)) { if (StrUtil.isEmpty(authCode)) {
throw exception0(BAD_REQUEST.getCode(), "条形码不能为空"); throw exception0(BAD_REQUEST.getCode(), "条形码不能为空");
@ -55,11 +55,15 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
request.setNotifyUrl(reqDTO.getNotifyUrl()); request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl()); request.setReturnUrl(reqDTO.getReturnUrl());
// TODO 芋艿各种边界的处理
// 2.1 执行请求 // 2.1 执行请求
AlipayTradePayResponse response = client.execute(request); AlipayTradePayResponse response = client.execute(request);
// 2.2 处理结果 // 2.2 处理结果
validateUnifiedOrderResponse(request, response); if (!response.isSuccess()) {
return new PayOrderUnifiedRespDTO(displayMode, ""); return buildClosedPayOrderRespDTO(reqDTO, response);
}
return new PayOrderRespDTO(displayMode, "",
reqDTO.getOutTradeNo(), response);
} }
} }

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.Method; import cn.hutool.http.Method;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
@ -29,7 +29,7 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
} }
@Override @Override
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 构建 AlipayTradePagePayModel 请求 // 1.1 构建 AlipayTradePagePayModel 请求
AlipayTradePagePayModel model = new AlipayTradePagePayModel(); AlipayTradePagePayModel model = new AlipayTradePagePayModel();
// 通用的参数 // 通用的参数
@ -60,8 +60,11 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
response = client.pageExecute(request, Method.GET.name()); response = client.pageExecute(request, Method.GET.name());
} }
// 2.2 处理结果 // 2.2 处理结果
validateUnifiedOrderResponse(request, response); if (!response.isSuccess()) {
return new PayOrderUnifiedRespDTO(displayMode, response.getBody()); return buildClosedPayOrderRespDTO(reqDTO, response);
}
return new PayOrderRespDTO(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
} }
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
@ -25,7 +25,7 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
} }
@Override @Override
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 构建 AlipayTradePrecreateModel 请求 // 1.1 构建 AlipayTradePrecreateModel 请求
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
// 通用的参数 // 通用的参数
@ -47,8 +47,11 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
// 2.1 执行请求 // 2.1 执行请求
AlipayTradePrecreateResponse response = client.execute(request); AlipayTradePrecreateResponse response = client.execute(request);
// 2.2 处理结果 // 2.2 处理结果
validateUnifiedOrderResponse(request, response); if (!response.isSuccess()) {
return new PayOrderUnifiedRespDTO(displayMode, response.getQrCode()); return buildClosedPayOrderRespDTO(reqDTO, response);
}
return new PayOrderRespDTO(displayMode, response.getQrCode(),
reqDTO.getOutTradeNo(), response);
} }
} }

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.http.Method; import cn.hutool.http.Method;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
@ -26,7 +26,7 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient {
} }
@Override @Override
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 构建 AlipayTradeWapPayModel 请求 // 1.1 构建 AlipayTradeWapPayModel 请求
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
// 通用的参数 // 通用的参数
@ -48,10 +48,12 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient {
// 2.1 执行请求 // 2.1 执行请求
AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name()); AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name());
// 2.2 处理结果 // 2.2 处理结果
validateUnifiedOrderResponse(request, response); if (!response.isSuccess()) {
return new PayOrderUnifiedRespDTO(displayMode, response.getBody()); return buildClosedPayOrderRespDTO(reqDTO, response);
}
return new PayOrderRespDTO(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
} }
} }

View File

@ -8,11 +8,9 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; 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.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; 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.WxPayOrderNotifyResult;
@ -35,9 +33,6 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static cn.hutool.core.date.DatePattern.*; import static cn.hutool.core.date.DatePattern.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2; import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2;
/** /**
@ -83,7 +78,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
// ============ 支付相关 ========== // ============ 支付相关 ==========
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
try { try {
switch (config.getApiVersion()) { switch (config.getApiVersion()) {
case API_VERSION_V2: case API_VERSION_V2:
@ -94,8 +89,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
} }
} catch (WxPayException e) { } catch (WxPayException e) {
// todo 芋艿异常的处理 String errorCode = getErrorCode(e);
throw buildUnifiedOrderException(reqDTO, e); String errorMessage = getErrorMessage(e);
return PayOrderRespDTO.build(errorCode, errorMessage,
reqDTO.getOutTradeNo(), e.getXmlString());
} }
} }
@ -105,8 +102,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
* @param reqDTO 下单信息 * @param reqDTO 下单信息
* @return 各支付渠道的返回结果 * @return 各支付渠道的返回结果
*/ */
protected abstract PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) protected abstract PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO)
throws WxPayException; throws Exception;
/** /**
* V3调用支付渠道统一下单 * V3调用支付渠道统一下单
@ -114,57 +111,42 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
* @param reqDTO 下单信息 * @param reqDTO 下单信息
* @return 各支付渠道的返回结果 * @return 各支付渠道的返回结果
*/ */
protected abstract PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) protected abstract PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO)
throws WxPayException; throws WxPayException;
@Override @Override
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) { public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws WxPayException {
try {
// 微信支付 v2 回调结果处理 // 微信支付 v2 回调结果处理
switch (config.getApiVersion()) { switch (config.getApiVersion()) {
case API_VERSION_V2: case API_VERSION_V2:
return parseOrderNotifyV2(body); return doParseOrderNotifyV2(body);
case WxPayClientConfig.API_VERSION_V3: case WxPayClientConfig.API_VERSION_V3:
return parseOrderNotifyV3(body); return doParseOrderNotifyV3(body);
default: default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
} }
} catch (WxPayException e) {
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
// throw buildPayException(e);
throw new RuntimeException(e);
// TODO 芋艿缺一个异常翻译
}
} }
private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException { private PayOrderRespDTO doParseOrderNotifyV2(String body) throws WxPayException {
// 1. 解析回调 // 1. 解析回调
WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body); WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
// 2. 构建结果 // 2. 构建结果
return PayOrderRespDTO.builder() Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ?
.outTradeNo(response.getOutTradeNo()) PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus();
.channelOrderNo(response.getTransactionId()) return new PayOrderRespDTO(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
.channelUserId(response.getOpenid()) response.getOutTradeNo(), body);
.status(Objects.equals(response.getResultCode(), "SUCCESS") ?
PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
.successTime(parseDateV2(response.getTimeEnd()))
.rawData(response)
.build();
} }
private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException { private PayOrderRespDTO doParseOrderNotifyV3(String body) throws WxPayException {
// 1. 解析回调 // 1. 解析回调
WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(body, null); WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(body, null);
WxPayOrderNotifyV3Result.DecryptNotifyResult responseResult = response.getResult(); WxPayOrderNotifyV3Result.DecryptNotifyResult result = response.getResult();
// 2. 构建结果 // 2. 构建结果
return PayOrderRespDTO.builder() Integer status = Objects.equals(result.getTradeState(), "SUCCESS") ?
.outTradeNo(responseResult.getOutTradeNo()) PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus();
.channelOrderNo(responseResult.getTradeState()) String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null;
.channelUserId(responseResult.getPayer() != null ? responseResult.getPayer().getOpenid() : null) return new PayOrderRespDTO(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()),
.status(Objects.equals(responseResult.getTradeState(), "SUCCESS") ? result.getOutTradeNo(), body);
PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
.successTime(parseDateV3(responseResult.getSuccessTime()))
.build();
} }
// ============ 退款相关 ========== // ============ 退款相关 ==========
@ -182,7 +164,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
} }
} catch (WxPayException e) { } catch (WxPayException e) {
// todo 芋艿异常的处理 // todo 芋艿异常的处理
throw buildUnifiedOrderException(null, e); // throw buildUnifiedOrderException(null, e);
return null;
} }
} }
@ -254,7 +237,6 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
} }
} catch (WxPayException e) { } catch (WxPayException e) {
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e); log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
// throw buildPayException(e);
throw new RuntimeException(e); throw new RuntimeException(e);
// TODO 芋艿缺一个异常翻译 // TODO 芋艿缺一个异常翻译
} }
@ -300,33 +282,6 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========
/**
* 构建统一下单的异常
*
* 目的将参数不正确等异常转换成 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 业务异常
*
* @param reqDTO 请求
* @param e 微信的支付异常
* @return 转换后的异常
*
*/
static Exception buildUnifiedOrderException(PayOrderUnifiedReqDTO reqDTO, WxPayException e) {
// 情况一业务结果为 FAIL
if (Objects.equals(e.getResultCode(), "FAIL")) {
log.error("[buildUnifiedOrderException][request({}) 发起支付失败]", toJsonString(reqDTO), e);
if (Objects.equals(e.getErrCode(), "PARAM_ERROR")) {
throw invalidParamException(e.getErrCodeDes());
}
throw exception(PayFrameworkErrorCodeConstants.ORDER_UNIFIED_ERROR, e.getErrCodeDes());
}
// 情况二状态码结果为 FAIL
if (Objects.equals(e.getReturnCode(), "FAIL")) {
throw exception(PayFrameworkErrorCodeConstants.ORDER_UNIFIED_ERROR, e.getReturnMsg());
}
// 情况三系统异常这里暂时不打交给上层的 AbstractPayClient 统一打日志
return e;
}
static String formatDateV2(LocalDateTime time) { static String formatDateV2(LocalDateTime time) {
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN); return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
} }
@ -347,4 +302,24 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN); return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN);
} }
static String getErrorCode(WxPayException e) {
if (StrUtil.isNotEmpty(e.getErrCode())) {
return e.getErrCode();
}
if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
return "CUSTOM_ERROR";
}
return e.getReturnCode();
}
static String getErrorMessage(WxPayException e) {
if (StrUtil.isNotEmpty(e.getErrCode())) {
return e.getErrCodeDes();
}
if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
return e.getCustomErrorMsg();
}
return e.getReturnMsg();
}
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.exception.WxPayException;
@ -19,12 +19,12 @@ public class WxAppPayClient extends AbstractWxPayClient {
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
return null; return null;
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
return null; return null;
} }

View File

@ -4,13 +4,10 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest; import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest;
import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.constant.WxPayConstants;
@ -22,6 +19,7 @@ import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/** /**
* 微信支付付款码支付 PayClient 实现类 * 微信支付付款码支付 PayClient 实现类
@ -48,7 +46,7 @@ public class WxBarPayClient extends AbstractWxPayClient {
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 由于付款码需要不断轮询所以需要在较短的时间完成支付 // 由于付款码需要不断轮询所以需要在较短的时间完成支付
LocalDateTime expireTime = LocalDateTimeUtils.addTime(AUTH_CODE_EXPIRE); LocalDateTime expireTime = LocalDateTimeUtils.addTime(AUTH_CODE_EXPIRE);
if (expireTime.isAfter(reqDTO.getExpireTime())) { if (expireTime.isAfter(reqDTO.getExpireTime())) {
@ -65,22 +63,16 @@ public class WxBarPayClient extends AbstractWxPayClient {
.authCode(getAuthCode(reqDTO)) .authCode(getAuthCode(reqDTO))
.build(); .build();
// 执行请求重试直到失败过期或者成功 // 执行请求重试直到失败过期或者成功
WxPayException lastWxPayException = null;
for (int i = 1; i < Byte.MAX_VALUE; i++) { for (int i = 1; i < Byte.MAX_VALUE; i++) {
try { try {
WxPayMicropayResult response = client.micropay(request); WxPayMicropayResult response = client.micropay(request);
// 支付成功例如说用户输入了密码 // 支付成功例如说1用户输入了密码2
PayOrderRespDTO order = PayOrderRespDTO.builder() return new PayOrderRespDTO(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
.status(PayOrderStatusRespEnum.SUCCESS.getStatus()) response.getOutTradeNo(), response)
.outTradeNo(response.getOutTradeNo()) .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode());
.channelOrderNo(response.getTransactionId())
.channelUserId(response.getOpenid())
.successTime(parseDateV2(response.getTimeEnd()))
.rawData(response)
.build();
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.BAR_CODE.getMode(),
JsonUtils.toJsonString(response))
.setOrder(order);
} catch (WxPayException ex) { } catch (WxPayException ex) {
lastWxPayException = ex;
// 如果不满足这 3 种任一的则直接抛出 WxPayException 异常不仅需处理 // 如果不满足这 3 种任一的则直接抛出 WxPayException 异常不仅需处理
// 1. SYSTEMERROR接口返回错误请立即调用被扫订单结果查询API查询当前订单状态并根据订单的状态决定下一步的操作 // 1. SYSTEMERROR接口返回错误请立即调用被扫订单结果查询API查询当前订单状态并根据订单的状态决定下一步的操作
// 2. USERPAYING用户支付中需要输入密码等待 5 然后调用被扫订单结果查询 API查询当前订单的不同状态决定下一步的操作 // 2. USERPAYING用户支付中需要输入密码等待 5 然后调用被扫订单结果查询 API查询当前订单的不同状态决定下一步的操作
@ -90,15 +82,15 @@ public class WxBarPayClient extends AbstractWxPayClient {
} }
// 等待 5 继续下一轮重新发起支付 // 等待 5 继续下一轮重新发起支付
log.info("[doUnifiedOrderV2][发起微信 Bar 支付第({})失败,等待下一轮重试,请求({}),响应({})]", i, log.info("[doUnifiedOrderV2][发起微信 Bar 支付第({})失败,等待下一轮重试,请求({}),响应({})]", i,
JsonUtils.toJsonString(request), ex.getMessage()); toJsonString(request), ex.getMessage());
ThreadUtil.sleep(5, TimeUnit.SECONDS); ThreadUtil.sleep(5, TimeUnit.SECONDS);
} }
} }
throw new IllegalStateException("微信 Bar 支付,重试多次失败"); throw lastWxPayException;
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
return doUnifiedOrderV2(reqDTO); return doUnifiedOrderV2(reqDTO);
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.exception.WxPayException;
@ -19,12 +19,12 @@ public class WxH5PayClient extends AbstractWxPayClient {
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
return null; return null;
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
return null; return null;
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
@ -32,7 +32,7 @@ public class WxNativePayClient extends AbstractWxPayClient {
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象 // 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.outTradeNo(reqDTO.getOutTradeNo()) .outTradeNo(reqDTO.getOutTradeNo())
@ -48,12 +48,12 @@ public class WxNativePayClient extends AbstractWxPayClient {
WxPayNativeOrderResult response = client.createOrder(request); WxPayNativeOrderResult response = client.createOrder(request);
// 转换结果 // 转换结果
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.QR_CODE.getMode(), return new PayOrderRespDTO(PayOrderDisplayModeEnum.QR_CODE.getMode(), response.getCodeUrl(),
response.getCodeUrl()); reqDTO.getOutTradeNo(), response);
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象 // 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request() WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request()
.setOutTradeNo(reqDTO.getOutTradeNo()) .setOutTradeNo(reqDTO.getOutTradeNo())
@ -66,8 +66,8 @@ public class WxNativePayClient extends AbstractWxPayClient {
String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);
// 转换结果 // 转换结果
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.QR_CODE.getMode(), return new PayOrderRespDTO(PayOrderDisplayModeEnum.QR_CODE.getMode(), response,
response); reqDTO.getOutTradeNo(), response);
} }
} }

View File

@ -2,9 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
@ -17,6 +16,7 @@ import com.github.binarywang.wxpay.exception.WxPayException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/** /**
* 微信支付公众号 PayClient 实现类 * 微信支付公众号 PayClient 实现类
@ -42,7 +42,7 @@ public class WxPubPayClient extends AbstractWxPayClient {
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象 // 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.outTradeNo(reqDTO.getOutTradeNo()) .outTradeNo(reqDTO.getOutTradeNo())
@ -58,12 +58,12 @@ public class WxPubPayClient extends AbstractWxPayClient {
WxPayMpOrderResult response = client.createOrder(request); WxPayMpOrderResult response = client.createOrder(request);
// 转换结果 // 转换结果
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.CUSTOM.getMode(), return new PayOrderRespDTO(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
JsonUtils.toJsonString(response)); reqDTO.getOutTradeNo(), response);
} }
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象 // 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
request.setOutTradeNo(reqDTO.getOutTradeNo()); request.setOutTradeNo(reqDTO.getOutTradeNo());
@ -77,8 +77,8 @@ public class WxPubPayClient extends AbstractWxPayClient {
WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request); WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request);
// 转换结果 // 转换结果
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.CUSTOM.getMode(), return new PayOrderRespDTO(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
JsonUtils.toJsonString(response)); reqDTO.getOutTradeNo(), response);
} }
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========

View File

@ -1,16 +0,0 @@
package cn.iocoder.yudao.framework.pay.core.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* 支付框架的错误码枚举
*
* 支付框架使用 2-002-000-000
*
* @author 芋道源码
*/
public interface PayFrameworkErrorCodeConstants {
ErrorCode ORDER_UNIFIED_ERROR = new ErrorCode(2002000000, "发起支付失败,原因:{}");
}

View File

@ -18,8 +18,7 @@ public enum PayOrderDisplayModeEnum {
QR_CODE("qr_code"), // 二维码的文字内容 QR_CODE("qr_code"), // 二维码的文字内容
QR_CODE_URL("qr_code_url"), // 二维码的图片链接 QR_CODE_URL("qr_code_url"), // 二维码的图片链接
BAR_CODE("bar_code"), // 条形码 BAR_CODE("bar_code"), // 条形码
APP("app"), // 应用目前暂时用不到 APP("app"), // 应用AndroidiOS微信小程序微信公众号等需要做自定义处理的
CUSTOM("custom"), // 自定义每种支付方式做个性化处理例如说微信公众号支付时调用 JSAPI 接口
; ;
/** /**

View File

@ -32,4 +32,14 @@ public enum PayOrderStatusRespEnum {
return Objects.equals(status, SUCCESS.getStatus()); return Objects.equals(status, SUCCESS.getStatus());
} }
/**
* 判断是否支付关闭
*
* @param status 状态
* @return 是否支付关闭
*/
public static boolean isClosed(Integer status) {
return Objects.equals(status, CLOSED.getStatus());
}
} }

View File

@ -351,7 +351,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
public void afterCommit() { public void afterCommit() {
// 创建退款单 // 创建退款单
PayRefundCreateReqDTO createReqDTO = TradeAfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties); PayRefundCreateReqDTO createReqDTO = TradeAfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties);
Long payRefundId = payRefundApi.createPayRefund(createReqDTO); Long payRefundId = payRefundApi.createRefund(createReqDTO);
// 更新售后单的退款单号 // 更新售后单的退款单号
tradeAfterSaleMapper.updateById(new TradeAfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId)); tradeAfterSaleMapper.updateById(new TradeAfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
} }

View File

@ -18,7 +18,7 @@ public interface PayRefundApi {
* @param reqDTO 创建请求 * @param reqDTO 创建请求
* @return 退款单编号 * @return 退款单编号
*/ */
Long createPayRefund(@Valid PayRefundCreateReqDTO reqDTO); Long createRefund(@Valid PayRefundCreateReqDTO reqDTO);
/** /**
* 获得退款单 * 获得退款单
@ -26,6 +26,6 @@ public interface PayRefundApi {
* @param id 退款单编号 * @param id 退款单编号
* @return 退款单 * @return 退款单
*/ */
PayRefundRespDTO getPayRefund(Long id); PayRefundRespDTO getRefund(Long id);
} }

View File

@ -27,6 +27,7 @@ public interface ErrorCodeConstants {
ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付"); ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付"); ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付");
ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1007002003, "支付订单已经过期"); ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1007002003, "支付订单已经过期");
ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1007002004, "发起支付报错,错误码:{},错误提示:{}");
// ========== ORDER 模块(拓展单) 1007003000 ========== // ========== ORDER 模块(拓展单) 1007003000 ==========
ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在"); ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");

View File

@ -22,12 +22,12 @@ public class PayRefundApiImpl implements PayRefundApi {
private PayRefundService payRefundService; private PayRefundService payRefundService;
@Override @Override
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) { public Long createRefund(PayRefundCreateReqDTO reqDTO) {
return payRefundService.createPayRefund(reqDTO); return payRefundService.createPayRefund(reqDTO);
} }
@Override @Override
public PayRefundRespDTO getPayRefund(Long id) { public PayRefundRespDTO getRefund(Long id) {
return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id)); return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id));
} }

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.pay.convert.order;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
@ -93,7 +92,8 @@ public interface PayOrderConvert {
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp); PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp);
PayOrderSubmitRespVO convert(PayOrderDO order, PayOrderUnifiedRespDTO unifiedRespDTO); @Mapping(source = "order.status", target = "status")
PayOrderSubmitRespVO convert(PayOrderDO order, cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO respDTO);
AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean); AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.dal.dataobject.order; package cn.iocoder.yudao.module.pay.dal.dataobject.order;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
@ -65,7 +66,6 @@ public class PayOrderExtensionDO extends BaseDO {
* 支付状态 * 支付状态
* *
* 枚举 {@link PayOrderStatusEnum} * 枚举 {@link PayOrderStatusEnum}
* 注意只包含上述枚举的 WAITING SUCCESS
*/ */
private Integer status; private Integer status;
/** /**
@ -75,10 +75,20 @@ public class PayOrderExtensionDO extends BaseDO {
*/ */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> channelExtras; private Map<String, String> channelExtras;
/** /**
* 支付渠道异步通知的内容 * 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道报错时错误信息
*/
private String channelErrorMsg;
/**
* 支付渠道的同步/异步通知的内容
* *
* 在支持成功后会记录回调的数据 * 对应 {@link PayOrderRespDTO#getRawData()}
*/ */
private String channelNotifyData; private String channelNotifyData;

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.pay.dal.dataobject.refund; package cn.iocoder.yudao.module.pay.dal.dataobject.refund;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
@ -151,9 +152,9 @@ public class PayRefundDO extends BaseDO {
private String channelErrorMsg; private String channelErrorMsg;
/** /**
* 支付渠道异步通知的内容 * 支付渠道的同步/异步通知的内容
* *
* 在退款成功后会记录回调的数据 * 对应 {@link PayRefundRespDTO#getRawData()}
*/ */
private String channelNotifyData; private String channelNotifyData;

View File

@ -189,7 +189,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
// 这里我们是个简单的 demo所以没有售后维权表直接使用订单 id + "-refund" 来演示 // 这里我们是个简单的 demo所以没有售后维权表直接使用订单 id + "-refund" 来演示
String refundId = order.getId() + "-refund"; String refundId = order.getId() + "-refund";
// 2.2 创建退款单 // 2.2 创建退款单
Long payRefundId = payRefundApi.createPayRefund(new PayRefundCreateReqDTO() Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
.setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用 .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
.setMerchantRefundId(refundId) .setMerchantRefundId(refundId)
@ -239,7 +239,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
} }
// 2.1 校验退款订单 // 2.1 校验退款订单
PayRefundRespDTO payRefund = payRefundApi.getPayRefund(payRefundId); PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
if (payRefund == null) { if (payRefund == null) {
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND); throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
} }

View File

@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair; import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.pay.config.PayProperties; import cn.iocoder.yudao.framework.pay.config.PayProperties;
@ -11,7 +12,6 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
@ -134,8 +134,7 @@ public class PayOrderServiceImpl implements PayOrderService {
return order.getId(); return order.getId();
} }
@Override @Override // 注意这里不能添加事务注解避免调用支付渠道失败时 PayOrderExtensionDO 回滚了
@Transactional(rollbackFor = Exception.class)
public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) { public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) {
// 1. 获得 PayOrderDO 并校验其是否存在 // 1. 获得 PayOrderDO 并校验其是否存在
PayOrderDO order = validateOrderCanSubmit(reqVO.getId()); PayOrderDO order = validateOrderCanSubmit(reqVO.getId());
@ -159,17 +158,20 @@ public class PayOrderServiceImpl implements PayOrderService {
.setReturnUrl(reqVO.getReturnUrl()) .setReturnUrl(reqVO.getReturnUrl())
// 订单相关字段 // 订单相关字段
.setPrice(order.getPrice()).setExpireTime(order.getExpireTime()); .setPrice(order.getPrice()).setExpireTime(order.getExpireTime());
PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO); PayOrderRespDTO unifiedOrderResp = client.unifiedOrder(unifiedOrderReqDTO);
// 4. 如果调用直接支付成功则直接更新支付单状态为成功例如说付款码支付免密支付时就直接验证支付成功 // 4. 如果调用直接支付成功则直接更新支付单状态为成功例如说付款码支付免密支付时就直接验证支付成功
if (unifiedOrderRespDTO.getOrder() != null) { if (unifiedOrderResp != null) {
notifyPayOrder(channel, unifiedOrderRespDTO.getOrder()); notifyPayOrder(channel, unifiedOrderResp);
// 如有渠道错误码则抛出业务异常提示用户
if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
unifiedOrderResp.getChannelErrorMsg());
}
// 此处需要读取最新的状态 // 此处需要读取最新的状态
order = orderMapper.selectById(order.getId()); order = orderMapper.selectById(order.getId());
} }
return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp);
// 返回成功
return PayOrderConvert.INSTANCE.convert(order, unifiedOrderRespDTO);
} }
private PayOrderDO validateOrderCanSubmit(Long id) { private PayOrderDO validateOrderCanSubmit(Long id) {
@ -269,8 +271,10 @@ public class PayOrderServiceImpl implements PayOrderService {
notifyOrderSuccess(channel, notify); notifyOrderSuccess(channel, notify);
return; return;
} }
// 情况二非支付成功的回调进行忽略 // 情况二支付失败的回调
log.info("[notifyPayOrder][非支付成功的回调({}),直接忽略]", toJsonString(notify)); if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) {
notifyOrderClosed(channel, notify);
}
} }
private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
@ -300,7 +304,7 @@ public class PayOrderServiceImpl implements PayOrderService {
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
} }
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功直接返回不用重复更新 if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功直接返回不用重复更新
log.info("[updateOrderExtensionSuccess][支付拓展单({}) 已经是已支付,无需更新为已支付]", orderExtension.getId()); log.info("[updateOrderExtensionSuccess][支付拓展单({}) 已经是已支付,无需更新]", orderExtension.getId());
return orderExtension; return orderExtension;
} }
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态必须是待支付 if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态必须是待支付
@ -335,7 +339,7 @@ public class PayOrderServiceImpl implements PayOrderService {
} }
if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功直接返回不用重复更新 if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功直接返回不用重复更新
&& Objects.equals(order.getSuccessExtensionId(), orderExtension.getId())) { && Objects.equals(order.getSuccessExtensionId(), orderExtension.getId())) {
log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新为已支付]", order.getId()); log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新]", order.getId());
return Pair.of(true, order); return Pair.of(true, order);
} }
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态必须是待支付 if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态必须是待支付
@ -356,4 +360,37 @@ public class PayOrderServiceImpl implements PayOrderService {
return Pair.of(false, order); return Pair.of(false, order);
} }
private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) {
updateOrderExtensionClosed(channel, notify);
}
private void updateOrderExtensionClosed(PayChannelDO channel, PayOrderRespDTO notify) {
// 1. 查询 PayOrderExtensionDO
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
if (orderExtension == null) {
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
}
if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭直接返回不用重复更新
log.info("[updateOrderExtensionClosed][支付拓展单({}) 已经是支付关闭,无需更新]", orderExtension.getId());
return;
}
// 一般出现先是支付成功然后支付关闭都是全部退款导致关闭的场景这个情况我们不更新支付拓展单只通过退款流程更新支付单
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
log.info("[updateOrderExtensionClosed][支付拓展单({}) 是已支付,无需更新为支付关闭]", orderExtension.getId());
return;
}
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态必须是待支付
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
}
// 2. 更新 PayOrderExtensionDO
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify))
.channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build());
if (updateCounts == 0) { // 校验状态必须是待支付
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
}
log.info("[updateOrderExtensionClosed][支付拓展单({}) 更新为支付关闭]", orderExtension.getId());
}
} }