mall + pay:

1. 完成支付宝的退款重构
2. 完成 demo 模块的退款接入
This commit is contained in:
YunaiV 2023-07-15 20:36:04 +08:00
parent 518e89dc4b
commit c44ace6011
23 changed files with 339 additions and 280 deletions

View File

@ -20,6 +20,13 @@ public class PayRefundRespDTO {
*/
private Integer status;
/**
* 外部退款号
*
* 对应 PayRefundDO no 字段
*/
private String outRefundNo;
/**
* 渠道退款单号
*

View File

@ -68,7 +68,6 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
model.setOutTradeNo(reqDTO.getOutTradeNo());
model.setOutRequestNo(reqDTO.getOutRefundNo());
model.setRefundAmount(formatAmount(reqDTO.getPrice()));
// model.setRefundAmount(formatAmount(reqDTO.getPrice() / 2));
model.setRefundReason(reqDTO.getReason());
// 1.2 构建 AlipayTradePayRequest 请求
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
@ -77,6 +76,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
// 2.1 执行请求
AlipayTradeRefundResponse response = client.execute(request);
PayRefundRespDTO refund = new PayRefundRespDTO()
.setOutRefundNo(reqDTO.getOutRefundNo())
.setRawData(response);
// 支付宝只要退款调用返回 success就认为退款成功不需要回调具体可见 parseNotify 方法的说明
// 另外支付宝没有退款单号所以不用设置

View File

@ -27,26 +27,32 @@ public class PayRefundCreateReqDTO {
private String userIp;
// ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单编号不能为空")
private String merchantOrderId;
/**
* 商户退款编号
*/
@NotEmpty(message = "商户退款编号不能为空")
private String merchantRefundId;
/**
* 退款描述
*/
@NotEmpty(message = "退款描述不能为空")
@Length(max = 128, message = "退款描述长度不能超过128")
@Length(max = 128, message = "退款描述长度不能超过 128")
private String reason;
// ========== 订单相关字段 ==========
/**
* 支付单号
*/
@NotNull(message = "支付单号不能为空")
private Long payOrderId;
/**
* 退款金额单位
*/
@NotNull(message = "退款金额不能为空")
@Min(value = 1, message = "退款金额必须大于零")
private Integer price;
}

View File

@ -33,11 +33,12 @@ public interface ErrorCodeConstants {
ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
// ========== 支付模块(退款) 1007006000 ==========
ErrorCode PAY_PRICE_PRICE_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
ErrorCode PAY_REFUND_PRICE_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空");
ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
ErrorCode PAY_REFUND_HAS_REFUNDING = new ErrorCode(1007006002, "已经有退款在处理中");
ErrorCode PAY_REFUND_EXISTS = new ErrorCode(1007006003, "已经存在退款单");
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
ErrorCode PAY_REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
// ========== 示例订单 1007900000 ==========
ErrorCode PAY_DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");

View File

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

View File

@ -22,7 +22,7 @@ public class PayOrderApiImpl implements PayOrderApi {
@Override
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
return payOrderService.createPayOrder(reqDTO);
return payOrderService.createOrder(reqDTO);
}
@Override

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.api.refund;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -27,8 +28,7 @@ public class PayRefundApiImpl implements PayRefundApi {
@Override
public PayRefundRespDTO getPayRefund(Long id) {
// TODO 芋艿暂未实现
return null;
return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id));
}
}

View File

@ -65,12 +65,12 @@ public class PayNotifyController {
// 3. 处理通知
// 3.1退款通知
if (notify instanceof PayRefundRespDTO) {
refundService.notifyPayRefund(channelId, (PayRefundRespDTO) notify);
refundService.notifyRefund(channelId, (PayRefundRespDTO) notify);
return "success";
}
// 3.2支付通知
if (notify instanceof PayOrderRespDTO) {
orderService.notifyPayOrder(channelId, (PayOrderRespDTO) notify);
orderService.notifyOrder(channelId, (PayOrderRespDTO) notify);
return "success";
}
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));

View File

@ -86,7 +86,7 @@ public class PayOrderController {
@PostMapping("/submit")
@Operation(summary = "提交支付订单")
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP());
return success(respVO);
}

View File

@ -40,7 +40,7 @@ public class AppPayOrderController {
@PostMapping("/submit")
@Operation(summary = "提交支付订单")
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP());
return success(PayOrderConvert.INSTANCE.convert3(respVO));
}

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.pay.convert.refund;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.math.BigDecimal;
@ -44,8 +43,6 @@ public interface PayRefundConvert {
PageResult<PayRefundRespVO> convertPage(PageResult<PayRefundDO> page);
List<PayRefundExcelVO> convertList02(List<PayRefundDO> list);
/**
* 退款订单DO 导出excel VO
*
@ -67,7 +64,6 @@ public interface PayRefundConvert {
payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl());
payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus());
payRefundExcelVO.setStatus(bean.getStatus());
payRefundExcelVO.setType(bean.getType());
payRefundExcelVO.setReason(bean.getReason());
payRefundExcelVO.setUserIp(bean.getUserIp());
payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
@ -84,12 +80,8 @@ public interface PayRefundConvert {
return payRefundExcelVO;
}
//TODO 太多需要处理了 暂时不用
@Mappings(value = {
@Mapping(source = "price", target = "payPrice"),
@Mapping(source = "id", target = "orderId"),
@Mapping(target = "status",ignore = true)
})
PayRefundDO convert(PayOrderDO orderDO);
PayRefundDO convert(PayRefundCreateReqDTO bean);
PayRefundRespDTO convert02(PayRefundDO bean);
}

View File

@ -6,7 +6,7 @@ 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.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@ -127,7 +127,7 @@ public class PayOrderDO extends BaseDO {
/**
* 退款状态
*
* 枚举 {@link PayRefundTypeEnum}
* 枚举 {@link PayOrderRefundStatusEnum}
*/
private Integer refundStatus;
/**
@ -137,7 +137,7 @@ public class PayOrderDO extends BaseDO {
/**
* 退款总金额单位
*/
private Long refundPrice;
private Integer refundPrice;
// ========== 渠道相关字段 ==========
/**

View File

@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -104,12 +103,6 @@ public class PayRefundDO extends BaseDO {
*/
private Integer status;
/**
* 退款类型(部分退款全部退款)
*
* 枚举 {@link PayRefundTypeEnum}
*/
private Integer type;
/**
* 支付金额单位
*/
@ -157,12 +150,6 @@ public class PayRefundDO extends BaseDO {
*/
private String channelErrorMsg;
/**
* 支付渠道的额外参数
*
* 参见 <a href="https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html">参数说明</>
*/
private String channelExtras;
/**
* 支付渠道异步通知的内容
*

View File

@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.pay.dal.mysql.refund;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@ -13,6 +15,34 @@ import java.util.List;
@Mapper
public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
default Long selectCountByAppId(Long appId) {
return selectCount(PayRefundDO::getAppId, appId);
}
default PayRefundDO selectByAppIdAndMerchantRefundId(Long appId, String merchantRefundId) {
return selectOne(new LambdaQueryWrapperX<PayRefundDO>()
.eq(PayRefundDO::getAppId, appId)
.eq(PayRefundDO::getMerchantRefundId, merchantRefundId));
}
default Long selectCountByAppIdAndOrderId(Long appId, Long orderId, Integer status) {
return selectCount(new LambdaQueryWrapperX<PayRefundDO>()
.eq(PayRefundDO::getAppId, appId)
.eq(PayRefundDO::getOrderId, orderId)
.eq(PayRefundDO::getStatus, status));
}
default PayRefundDO selectByAppIdAndNo(Long appId, String no) {
return selectOne(new LambdaQueryWrapperX<PayRefundDO>()
.eq(PayRefundDO::getAppId, appId)
.eq(PayRefundDO::getNo, no));
}
default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) {
return update(update, new LambdaQueryWrapper<PayRefundDO>()
.eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status));
}
default PageResult<PayRefundDO> selectPage(PayRefundPageReqVO reqVO) {
return selectPage(reqVO, new QueryWrapperX<PayRefundDO>()
.eqIfPresent("app_id", reqVO.getAppId())
@ -37,18 +67,4 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
.orderByDesc("id"));
}
default Long selectCountByApp(Long appId) {
return selectCount(PayRefundDO::getAppId, appId);
}
default PayRefundDO selectByReqNo(String reqNo) {
return selectOne("req_no", reqNo);
}
// TODO 芋艿要重构
default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
// return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
return null;
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.pay.enums.refund;
package cn.iocoder.yudao.module.pay.enums.order;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
@ -11,10 +11,10 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
public enum PayRefundTypeEnum implements IntArrayValuable {
public enum PayOrderRefundStatusEnum implements IntArrayValuable {
NO(0, "未退款"),
SOME(10, "部分退款"),
PART(10, "部分退款"),
ALL(20, "全部退款")
;

View File

@ -184,12 +184,17 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
// 1. 校验订单是否可以退款
PayDemoOrderDO order = validateDemoOrderCanRefund(id);
// 2.1 创建退款单
// 2.1 生成退款单号
// 一般来说用户发起退款的时候都会单独插入一个售后维权表然后使用该表的 id 作为 refundId
// 这里我们是个简单的 demo所以没有售后维权表直接使用订单 id + "-refund" 来演示
String refundId = order.getId() + "-refund";
// 2.2 创建退款单
Long payRefundId = payRefundApi.createPayRefund(new PayRefundCreateReqDTO()
.setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
.setPayOrderId(order.getPayOrderId()) // 支付单号
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
.setMerchantRefundId(refundId)
.setReason("想退钱").setPrice(order.getPrice()));// 价格信息
// 2.2 更新退款单到 demo 订单
// 2.3 更新退款单到 demo 订单
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
.setPayRefundId(payRefundId).setRefundPrice(order.getPrice()));
}

View File

@ -7,6 +7,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
// TODO 芋艿合并到 PayOrder
/**
* 支付订单 Service 接口
*

View File

@ -31,6 +31,15 @@ public interface PayOrderService {
*/
PayOrderDO getOrder(Long id);
/**
* 获得支付订单
*
* @param appId 应用编号
* @param merchantOrderId 商户订单编号
* @return 支付订单
*/
PayOrderDO getOrder(Long appId, String merchantOrderId);
/**
* 获得指定应用的订单数量
*
@ -70,11 +79,11 @@ public interface PayOrderService {
/**
* 根据订单 ID 集合获取订单商品名称Map集合
*
* @param idList 订单 ID 集合
* @param ids 订单 ID 集合
* @return 订单商品 map 集合
*/
default Map<Long, PayOrderDO> getOrderSubjectMap(Collection<Long> idList) {
List<PayOrderDO> list = getOrderSubjectList(idList);
default Map<Long, PayOrderDO> getOrderSubjectMap(Collection<Long> ids) {
List<PayOrderDO> list = getOrderSubjectList(ids);
return CollectionUtils.convertMap(list, PayOrderDO::getId);
}
@ -84,7 +93,7 @@ public interface PayOrderService {
* @param reqDTO 创建请求
* @return 支付单编号
*/
Long createPayOrder(@Valid PayOrderCreateReqDTO reqDTO);
Long createOrder(@Valid PayOrderCreateReqDTO reqDTO);
/**
* 提交支付
@ -94,8 +103,8 @@ public interface PayOrderService {
* @param userIp 提交 IP
* @return 提交结果
*/
PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
@NotEmpty(message = "提交 IP 不能为空") String userIp);
PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO,
@NotEmpty(message = "提交 IP 不能为空") String userIp);
/**
* 通知支付单成功
@ -103,6 +112,14 @@ public interface PayOrderService {
* @param channelId 渠道编号
* @param notify 通知
*/
void notifyPayOrder(Long channelId, PayOrderRespDTO notify);
void notifyOrder(Long channelId, PayOrderRespDTO notify);
/**
* 更新支付订单的退款金额
*
* @param id 编号
* @param incrRefundPrice 增加的退款金额
*/
void updateOrderRefundPrice(Long id, Integer incrRefundPrice);
}

View File

@ -26,11 +26,10 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@ -48,6 +47,7 @@ import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
/**
* 支付订单 Service 实现类
@ -82,6 +82,11 @@ public class PayOrderServiceImpl implements PayOrderService {
return orderMapper.selectById(id);
}
@Override
public PayOrderDO getOrder(Long appId, String merchantOrderId) {
return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId);
}
@Override
public Long getOrderCountByAppId(Long appId) {
return orderMapper.selectCountByAppId(appId);
@ -104,7 +109,7 @@ public class PayOrderServiceImpl implements PayOrderService {
}
@Override
public Long createPayOrder(PayOrderCreateReqDTO reqDTO) {
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
// 校验 App
PayAppDO app = appService.validPayApp(reqDTO.getAppId());
@ -112,7 +117,7 @@ public class PayOrderServiceImpl implements PayOrderService {
PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId(
reqDTO.getAppId(), reqDTO.getMerchantOrderId());
if (order != null) {
log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
order.getMerchantOrderId(), toJsonString(order)); // 理论来说不会出现这个情况
return order.getId();
}
@ -124,16 +129,16 @@ public class PayOrderServiceImpl implements PayOrderService {
// 订单相关字段
.setStatus(PayOrderStatusEnum.WAITING.getStatus())
// 退款相关字段
.setRefundStatus(PayRefundTypeEnum.NO.getStatus()).setRefundTimes(0).setRefundPrice(0L);
.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()).setRefundTimes(0).setRefundPrice(0);
orderMapper.insert(order);
return order.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) {
// 1. 获得 PayOrderDO 并校验其是否存在
PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
PayOrderDO order = validateOrderCanSubmit(reqVO.getId());
// 1.2 校验支付渠道是否有效
PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
PayClient client = payClientFactory.getPayClient(channel.getId());
@ -167,16 +172,16 @@ public class PayOrderServiceImpl implements PayOrderService {
return PayOrderConvert.INSTANCE.convert(order, unifiedOrderRespDTO);
}
private PayOrderDO validatePayOrderCanSubmit(Long id) {
private PayOrderDO validateOrderCanSubmit(Long id) {
PayOrderDO order = orderMapper.selectById(id);
if (order == null) { // 是否存在
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
throw exception(PAY_ORDER_NOT_FOUND);
}
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
}
if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期
throw exception(ErrorCodeConstants.PAY_ORDER_IS_EXPIRED);
throw exception(PAY_ORDER_IS_EXPIRED);
}
return order;
}
@ -184,14 +189,12 @@ public class PayOrderServiceImpl implements PayOrderService {
private PayChannelDO validatePayChannelCanSubmit(Long appId, String channelCode) {
// 校验 App
appService.validPayApp(appId);
// 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
}
return channel;
}
@ -226,28 +229,55 @@ public class PayOrderServiceImpl implements PayOrderService {
@Override
@Transactional(rollbackFor = Exception.class)
public void notifyPayOrder(Long channelId, PayOrderRespDTO notify) {
public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
// 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(channelId);
// 更新支付订单为已支付
TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrder(channel, notify));
}
@Override
public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) {
PayOrderDO order = orderMapper.selectById(id);
if (order == null) {
throw exception(PAY_ORDER_NOT_FOUND);
}
if (!PayOrderStatusEnum.isSuccess(order.getStatus())) {
throw exception(PAY_REFUND_PRICE_EXCEED);
}
if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) {
throw exception(PAY_REFUND_PRICE_EXCEED);
}
// 更新订单
PayOrderDO updateObj = new PayOrderDO()
.setRefundPrice(order.getRefundPrice() + incrRefundPrice)
.setRefundTimes(order.getRefundTimes() + 1);
if (Objects.equals(updateObj.getRefundPrice(), order.getPrice())) {
updateObj.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus());
} else {
updateObj.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
.setRefundStatus(PayOrderRefundStatusEnum.PART.getStatus());
}
orderMapper.updateByIdAndStatus(id, PayOrderStatusEnum.SUCCESS.getStatus(), updateObj);
}
private void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) {
// 情况一支付成功的回调
if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
notifyPayOrderSuccess(channel, notify);
notifyOrderSuccess(channel, notify);
return;
}
// 情况二非支付成功的回调进行忽略
log.info("[notifyPayOrder][非支付成功的回调({}),直接忽略]", toJsonString(notify));
}
private void notifyPayOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
// 1. 更新 PayOrderExtensionDO 支付成功
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify);
PayOrderExtensionDO orderExtension = updateOrderExtensionSuccess(notify);
// 2. 更新 PayOrderDO 支付成功
Pair<Boolean, PayOrderDO> order = updatePayOrderSuccess(channel, orderExtension, notify);
Pair<Boolean, PayOrderDO> order = updateOrderExtensionSuccess(channel, orderExtension, notify);
if (order.getKey()) { // 如果之前已经成功回调则直接返回不用重复记录支付通知记录例如说支付平台重复回调
return;
}
@ -263,27 +293,27 @@ public class PayOrderServiceImpl implements PayOrderService {
* @param notify 通知
* @return PayOrderExtensionDO 对象
*/
private PayOrderExtensionDO updatePayOrderExtensionSuccess(PayOrderRespDTO notify) {
private PayOrderExtensionDO updateOrderExtensionSuccess(PayOrderRespDTO notify) {
// 1. 查询 PayOrderExtensionDO
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
if (orderExtension == null) {
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
}
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功直接返回不用重复更新
log.info("[updatePayOrderSuccess][支付拓展单({}) 已经是已支付,无需更新为已支付]", orderExtension.getId());
log.info("[updateOrderExtensionSuccess][支付拓展单({}) 已经是已支付,无需更新为已支付]", orderExtension.getId());
return orderExtension;
}
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
}
// 2. 更新 PayOrderExtensionDO
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(),
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build());
if (updateCounts == 0) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
}
log.info("[updatePayOrderSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId());
log.info("[updateOrderExtensionSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId());
return orderExtension;
}
@ -296,20 +326,20 @@ public class PayOrderServiceImpl implements PayOrderService {
* @return key是否之前已经成功回调
* valuePayOrderDO 对象
*/
private Pair<Boolean, PayOrderDO> updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
private Pair<Boolean, PayOrderDO> updateOrderExtensionSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
PayOrderRespDTO notify) {
// 1. 判断 PayOrderDO 是否处于待支付
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
if (order == null) {
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
throw exception(PAY_ORDER_NOT_FOUND);
}
if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功直接返回不用重复更新
&& Objects.equals(order.getSuccessExtensionId(), orderExtension.getId())) {
log.info("[updatePayOrderSuccess][支付订单({}) 已经是已支付,无需更新为已支付]", order.getId());
log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新为已支付]", order.getId());
return Pair.of(true, order);
}
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
}
// 2. 更新 PayOrderDO
@ -320,9 +350,9 @@ public class PayOrderServiceImpl implements PayOrderService {
.channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
.notifyTime(LocalDateTime.now()).build());
if (updateCounts == 0) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
}
log.info("[updatePayOrderSuccess][支付订单({}) 更新为已支付]", order.getId());
log.info("[updateOrderExtensionSuccess][支付订单({}) 更新为已支付]", order.getId());
return Pair.of(false, order);
}

View File

@ -62,6 +62,6 @@ public interface PayRefundService {
* @param channelId 渠道编号
* @param notify 通知
*/
void notifyPayRefund(Long channelId, PayRefundRespDTO notify);
void notifyRefund(Long channelId, PayRefundRespDTO notify);
}

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.module.pay.service.refund;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.config.PayProperties;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
@ -10,22 +9,23 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
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.enums.refund.PayRefundStatusRespEnum;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
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.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@ -38,8 +38,11 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* 退款订单 Service 实现类
@ -59,8 +62,6 @@ public class PayRefundServiceImpl implements PayRefundService {
@Resource
private PayRefundMapper refundMapper;
@Resource
private PayOrderMapper orderMapper; // TODO @jason需要改成不直接操作 db
@Resource
private PayOrderService orderService;
@ -80,7 +81,7 @@ public class PayRefundServiceImpl implements PayRefundService {
@Override
public Long getRefundCountByAppId(Long appId) {
return refundMapper.selectCountByApp(appId);
return refundMapper.selectCountByAppId(appId);
}
@Override
@ -96,82 +97,82 @@ public class PayRefundServiceImpl implements PayRefundService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
// 获得 PayOrderDO
PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId());
// 校验订单是否存在
if (Objects.isNull(order) ) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
}
// 校验 App
PayAppDO app = appService.validPayApp(order.getAppId());
// 校验支付渠道是否有效
// 1.1 校验 App
PayAppDO app = appService.validPayApp(reqDTO.getAppId());
// 1.2 校验支付订单
PayOrderDO order = validatePayOrderCanRefund(reqDTO);
// 1.3 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(order.getChannelId());
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
}
// 1.4 校验退款订单是否已经存在
PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId(
app.getId(), reqDTO.getMerchantRefundId());
if (refund != null) {
throw exception(ErrorCodeConstants.PAY_REFUND_EXISTS);
}
// TODO 芋艿待实现
String merchantRefundId = "rrr" + RandomUtil.randomNumbers(16);
// 校验退款的条件
validatePayRefund(reqDTO, order);
// 退款类型
PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
if (Objects.equals(reqDTO.getPrice(), order.getPrice())) {
refundType = PayRefundTypeEnum.ALL;
}
PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
merchantRefundId); // TODO 芋艿需要优化
if (Objects.nonNull(payRefundDO)) {
// 退款订单已经提交过
//TODO 校验相同退款单的金额
// TODO @jason咱要不封装一个 ObjectUtils.equalsAny
if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())) {
//已成功退款
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_SUCCEED);
}
//可以重复提交保证 退款请求号 一致由渠道保证幂等
} else {
// 成功插入退款单 状态为生成.没有和渠道交互
// TODO @jason搞到 convert 一些额外的自动手动 set
payRefundDO = PayRefundDO.builder()
.appId(order.getAppId())
.channelOrderNo(order.getChannelOrderNo())
.channelCode(order.getChannelCode())
.channelId(order.getChannelId())
.orderId(order.getId())
.merchantRefundId(merchantRefundId)
.notifyUrl(app.getRefundNotifyUrl())
.payPrice(order.getPrice())
.refundPrice(reqDTO.getPrice())
.userIp(reqDTO.getUserIp())
.merchantOrderId(order.getMerchantOrderId())
.no(orderExtensionDO.getNo())
.status(PayRefundStatusEnum.WAITING.getStatus())
.reason(reqDTO.getReason())
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
.type(refundType.getStatus())
.build();
refundMapper.insert(payRefundDO);
}
// TODO @jason搞到 convert 一些额外的自动手动 set
// 2.1 插入退款单
refund = PayRefundConvert.INSTANCE.convert(reqDTO)
.setNo(generateRefundNo()).setOrderId(order.getId())
.setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode())
// 商户相关的字段
.setNotifyUrl(app.getRefundNotifyUrl()).setNotifyStatus(PayNotifyStatusEnum.WAITING.getStatus())
// 渠道相关字段
.setChannelOrderNo(order.getChannelOrderNo())
// 退款相关字段
.setStatus(PayRefundStatusEnum.WAITING.getStatus())
.setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice());
refundMapper.insert(refund);
// 2.2 向渠道发起退款申请
PayOrderExtensionDO orderExtension = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
unifiedReqDTO.setPrice(reqDTO.getPrice())
.setOutTradeNo(orderExtensionDO.getNo())
.setOutRefundNo(merchantRefundId) // TODO 芋艿需要优化
.setOutTradeNo(orderExtension.getNo())
.setOutRefundNo(refund.getNo())
.setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿优化下 notifyUrl
.setReason(reqDTO.getReason());
// 向渠道发起退款申请
client.unifiedRefund(unifiedReqDTO);
// 检查是否失败失败抛出业务异常
// TODO 渠道的异常记录
// TODO @jason可以先打个 warn log
PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); // TODO 增加一个 channelErrorCodechannelErrorMsg 字段
// 2.3 处理退款返回
notifyRefund(channel, refundRespDTO);
// 成功在 退款回调中处理
return payRefundDO.getId();
return refund.getId();
}
/**
* 校验支付订单是否可以退款
*
* @param reqDTO 退款申请信息
* @return 支付订单
*/
private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) {
PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId());
if (order == null) {
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
}
// 校验状态必须是支付状态
if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS);
}
// 是否已经全额退款
if (PayOrderRefundStatusEnum.ALL.getStatus().equals(order.getRefundStatus())) {
throw exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED);
}
// 校验金额 退款金额不能大于原定的金额
if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
throw exception(ErrorCodeConstants.PAY_REFUND_PRICE_EXCEED);
}
// 是否有退款中的订单
if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(),
PayRefundStatusEnum.WAITING.getStatus()) > 0) {
throw exception(ErrorCodeConstants.PAY_REFUND_HAS_REFUNDING);
}
return order;
}
/**
@ -184,88 +185,80 @@ public class PayRefundServiceImpl implements PayRefundService {
return payProperties.getCallbackUrl() + "/" + channel.getId();
}
private String generateRefundNo() {
// wx
// 2014
// 10
// 27
// 20
// 09
// 39
// 5522657
// a690389285100
// 目前的算法
// 时间序列年月日时分秒 14
// 纯随机6 TODO 芋艿此处估计是会有问题的后续在调整
return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + // 时间序列
RandomUtil.randomInt(100000, 999999) // 随机为什么是这个范围因为偷懒
;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void notifyPayRefund(Long channelId, PayRefundRespDTO notify) {
public void notifyRefund(Long channelId, PayRefundRespDTO notify) {
// 校验支付渠道是否有效
channelService.validPayChannel(channelId);
// 通知结果
// 校验支付渠道是否有效
// TODO 芋艿需要重构下这块的逻辑
PayChannelDO channel = channelService.validPayChannel(channelId);
if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
payRefundSuccess(notify);
} else {
// TODO @jason那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
}
}
private void payRefundSuccess(PayRefundRespDTO refundNotify) {
// 校验退款单存在
PayRefundDO refundDO = null; // TODO 芋艿临时注释
// PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
// refundNotify.getReqNo());
if (refundDO == null) {
// TODO 芋艿临时注释
// log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
}
// 得到已退金额
PayOrderDO payOrderDO = orderService.getOrder(refundDO.getOrderId());
Long refundedAmount = payOrderDO.getRefundPrice();
PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
if(Objects.equals(payOrderDO.getPrice(), refundedAmount+ refundDO.getRefundPrice())){
//支付金额 = 已退金额 + 本次退款金额
orderStatus = PayOrderStatusEnum.CLOSED;
}
// 更新支付订单
PayOrderDO updateOrderDO = new PayOrderDO();
updateOrderDO.setId(refundDO.getOrderId())
.setRefundPrice(refundedAmount + refundDO.getRefundPrice())
.setStatus(orderStatus.getStatus())
.setRefundTimes(payOrderDO.getRefundTimes() + 1)
.setRefundStatus(refundDO.getType());
orderMapper.updateById(updateOrderDO);
// 更新退款订单
PayRefundDO updateRefundDO = new PayRefundDO();
updateRefundDO.setId(refundDO.getId())
.setSuccessTime(refundNotify.getSuccessTime())
// TODO 芋艿如下两行临时注释
// .setChannelRefundNo(refundNotify.getChannelOrderNo())
// .setNo(refundNotify.getTradeNo())
.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
refundMapper.updateById(updateRefundDO);
// 插入退款通知记录
// TODO 通知商户成功或者失败. 现在通知似乎没有实现 只是回调
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
.type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
TenantUtils.execute(channel.getTenantId(), () -> notifyRefund(channel, notify));
}
/**
* 校验是否进行退款
*
* @param reqDTO 退款申请信息
* @param order 原始支付订单信息
*/
private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) {
// 校验状态必须是支付状态
if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS);
private void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) {
if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
notifyRefundSuccess(channel, notify);
} else {
notifyRefundFailure(channel, notify);
}
// 是否已经全额退款
if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED);
}
private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
// 1.1 查询 PayRefundDO
PayRefundDO refund = refundMapper.selectByAppIdAndNo(
channel.getAppId(), notify.getOutRefundNo());
if (refund == null) {
throw exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
}
// 校验金额 退款金额不能大于 原定的金额
if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_PRICE_PRICE_EXCEED);
if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功直接返回不用重复更新
return;
}
// 校验渠道订单号
if (StrUtil.isEmpty(order.getChannelOrderNo())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_CHN_ORDER_NO_IS_NULL);
if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) {
throw exception(ErrorCodeConstants.PAY_REFUND_STATUS_IS_NOT_WAITING);
}
//TODO 退款的期限 退款次数的控制
// 1.2 更新 PayRefundDO
PayRefundDO updateRefundObj = new PayRefundDO()
.setSuccessTime(notify.getSuccessTime())
.setChannelRefundNo(notify.getChannelRefundNo())
.setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
.setChannelNotifyData(toJsonString(notify));
int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj);
if (updateCounts == 0) { // 校验状态必须是等待状态
throw exception(ErrorCodeConstants.PAY_REFUND_STATUS_IS_NOT_WAITING);
}
// 2. 更新订单
orderService.updateOrderRefundPrice(refund.getOrderId(), refund.getRefundPrice());
// 3. 插入退款通知记录
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
.type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build());
}
private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
// TODO 芋艿未实现
}
}

View File

@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@ -85,7 +85,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
o.setSuccessTime(LocalDateTime.of(2018, 1, 1, 10, 10, 2));
o.setNotifyTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15));
o.setSuccessExtensionId(1L);
o.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
o.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
o.setRefundTimes(0);
o.setRefundPrice(0L);
o.setChannelUserId("1008611");
@ -106,7 +106,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
// 测试 status 不匹配
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
// 测试 refundStatus 不匹配
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus())));
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus())));
// 测试 createTime 不匹配
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(LocalDateTime.of(2019, 1, 1, 10, 10,
1))));
@ -118,7 +118,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
reqVO.setMerchantOrderId(merchantOrderId);
reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
reqVO.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2018, 1, 1, 10, 1, 0), LocalDateTime.of(2018, 1, 1, 10, 1, 0)}));
// 调用
PageResult<PayOrderDO> pageResult = orderService.getOrderPage(reqVO);
@ -153,7 +153,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
o.setSuccessTime(LocalDateTime.of(2018, 1, 1, 10, 10, 2));
o.setNotifyTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15));
o.setSuccessExtensionId(1L);
o.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
o.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
o.setRefundTimes(0);
o.setRefundPrice(0L);
o.setChannelUserId("1008611");
@ -175,7 +175,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
// 测试 status 不匹配
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
// 测试 refundStatus 不匹配
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus())));
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus())));
// 测试 createTime 不匹配
orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(LocalDateTime.of(2019, 1, 1, 10, 10,
1))));
@ -187,7 +187,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
reqVO.setMerchantOrderId(merchantOrderId);
reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
reqVO.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2018, 1, 1, 10, 1, 0), LocalDateTime.of(2018, 1, 1, 10, 1, 0)}));
// 调用

View File

@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@ -64,11 +64,11 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
o.setOrderId(1L);
o.setNo("OT0000001");
o.setMerchantOrderId("MOT0000001");
o.setMerchantRefundNo("MRF0000001");
o.setMerchantRefundId("MRF0000001");
o.setNotifyUrl("https://www.cancanzi.com");
o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
o.setType(PayRefundTypeEnum.SOME.getStatus());
o.setType(PayOrderRefundStatusEnum.PART.getStatus());
o.setPayPrice(100);
o.setRefundPrice(500);
o.setReason("就是想退款了,你有意见吗");
@ -77,10 +77,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
o.setChannelRefundNo("CHR0000001");
o.setChannelErrorCode("");
o.setChannelErrorMsg("");
o.setChannelExtras("");
o.setExpireTime(LocalDateTime.of(2021, 1, 1, 10, 10, 30));
o.setSuccessTime(LocalDateTime.of(2021, 1, 1, 10, 10, 15));
o.setNotifyTime(LocalDateTime.of(2021, 1, 1, 10, 10, 20));
o.setCreateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 10));
o.setUpdateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 35));
});
@ -90,14 +87,14 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
// 测试 channelCode 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
// 测试 merchantRefundNo 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112")));
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112")));
// 测试 notifyStatus 不匹配
refundMapper.insert(
cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
// 测试 status 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus())));
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus())));
// 测试 type 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus())));
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayOrderRefundStatusEnum.ALL.getStatus())));
// 测试 createTime 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o ->
o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10))));
@ -108,7 +105,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
reqVO.setMerchantRefundNo("MRF0000001");
reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
reqVO.setType(PayRefundTypeEnum.SOME.getStatus());
reqVO.setType(PayOrderRefundStatusEnum.PART.getStatus());
reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)}));
// 调用
@ -129,11 +126,11 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
o.setOrderId(1L);
o.setNo("OT0000001");
o.setMerchantOrderId("MOT0000001");
o.setMerchantRefundNo("MRF0000001");
o.setMerchantRefundId("MRF0000001");
o.setNotifyUrl("https://www.cancanzi.com");
o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
o.setType(PayRefundTypeEnum.SOME.getStatus());
o.setType(PayOrderRefundStatusEnum.PART.getStatus());
o.setPayPrice(100);
o.setRefundPrice(500);
o.setReason("就是想退款了,你有意见吗");
@ -142,10 +139,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
o.setChannelRefundNo("CHR0000001");
o.setChannelErrorCode("");
o.setChannelErrorMsg("");
o.setChannelExtras("");
o.setExpireTime(LocalDateTime.of(2021, 1, 1, 10, 10, 30));
o.setSuccessTime(LocalDateTime.of(2021, 1, 1, 10, 10, 15));
o.setNotifyTime(LocalDateTime.of(2021, 1, 1, 10, 10, 20));
o.setCreateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 10));
o.setUpdateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 35));
});
@ -155,14 +149,14 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
// 测试 channelCode 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
// 测试 merchantRefundNo 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112")));
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112")));
// 测试 notifyStatus 不匹配
refundMapper.insert(
cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
// 测试 status 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus())));
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus())));
// 测试 type 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus())));
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayOrderRefundStatusEnum.ALL.getStatus())));
// 测试 createTime 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o ->
o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10))));
@ -174,7 +168,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
reqVO.setMerchantRefundNo("MRF0000001");
reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
reqVO.setType(PayRefundTypeEnum.SOME.getStatus());
reqVO.setType(PayOrderRefundStatusEnum.PART.getStatus());
reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)}));
// 调用