Commit cd2bc112cc4a12e0172beaa99f4ed6224d391669

Authored by YunaiV
1 parent ee1d362a

trade:【交易售后】完善发起、同意、不同意、收货、拒绝收货、退款的逻辑

Showing 15 changed files with 261 additions and 192 deletions
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
@@ -32,11 +32,9 @@ public interface ErrorCodeConstants { @@ -32,11 +32,9 @@ public interface ErrorCodeConstants {
32 ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1011000105, "订单项已申请售后,无法重复申请"); 32 ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1011000105, "订单项已申请售后,无法重复申请");
33 ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1011000106, "审批失败,售后状态不处于审批中"); 33 ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1011000106, "审批失败,售后状态不处于审批中");
34 ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1011000107, "操作售后单失败,请刷新后重试"); 34 ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1011000107, "操作售后单失败,请刷新后重试");
35 - ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_PASS = new ErrorCode(1011000108, "退货失败,售后单状态不处于【待买家退货】");  
36 - ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_RETURN = new ErrorCode(1011000109, "确认收货失败,售后单状态不处于【待确认收货】");  
37 - ErrorCode AFTER_SALE_REFUND_FAIL_PAY_REFUND_NOT_FOUND = new ErrorCode(1011000110, "退款失败,支付退款单不存在");  
38 - ErrorCode AFTER_SALE_REFUND_FAIL_PAY_REFUND_STATUS_NOT_SUCCESS = new ErrorCode(1011000111, "退款失败,支付退款单状态不是【成功】");  
39 - ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1011000112, "退款失败,售后单状态不是【待退款】"); 35 + ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1011000108, "退货失败,售后单状态不处于【待买家退货】");
  36 + ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1011000109, "确认收货失败,售后单状态不处于【待确认收货】");
  37 + ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1011000110, "退款失败,售后单状态不是【待退款】");
40 38
41 // ========== Cart 模块 1-011-001-000 ========== 39 // ========== Cart 模块 1-011-001-000 ==========
42 ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1011002000, "购物车项不存在"); 40 ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1011002000, "购物车项不存在");
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleStatusEnum.java
@@ -15,14 +15,14 @@ import lombok.Getter; @@ -15,14 +15,14 @@ import lombok.Getter;
15 public enum TradeAfterSaleStatusEnum { 15 public enum TradeAfterSaleStatusEnum {
16 16
17 APPLY(10,"申请中"), 17 APPLY(10,"申请中"),
18 - SELLER_PASS(20, "已通过"), // 卖家通过售后  
19 - BUYER_RETURN(30,"待卖家收货"), // 买家退货,等待卖家收货  
20 - WAIT_REFUND(40, "等待平台退款"), // 卖家收货,等待平台退款 18 + SELLER_AGREE(20, "卖家通过"), // 卖家通过售后
  19 + BUYER_DELIVERY(30,"待卖家收货"), // 买家已退货,等待卖家收货
  20 + WAIT_REFUND(40, "等待平台退款"), // 卖家已收货,等待平台退款
21 COMPLETE(50, "完成"), // 完成退款 21 COMPLETE(50, "完成"), // 完成退款
22 22
23 BUYER_CANCEL(61, "买家取消售后"), 23 BUYER_CANCEL(61, "买家取消售后"),
24 - SELLER_REFUSE(62,"已拒绝"), // 卖家拒绝售后  
25 - SELLER_TERMINATION(63,"卖家终止售后"), // 卖家拒绝收货,终止售后 24 + SELLER_DISAGREE(62,"卖家拒绝"), // 卖家拒绝售后
  25 + SELLER_REFUSE(63,"卖家拒绝收货"), // 卖家拒绝收货,终止售后
26 ; 26 ;
27 27
28 /** 28 /**
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderRefundStatusEnum.java renamed to yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderAfterSaleStatusEnum.java
@@ -4,13 +4,13 @@ import lombok.Getter; @@ -4,13 +4,13 @@ import lombok.Getter;
4 import lombok.RequiredArgsConstructor; 4 import lombok.RequiredArgsConstructor;
5 5
6 /** 6 /**
7 - * 交易订单 - 退款状态 7 + * 交易订单 - 售后状态
8 * 8 *
9 * @author Sin 9 * @author Sin
10 */ 10 */
11 @RequiredArgsConstructor 11 @RequiredArgsConstructor
12 @Getter 12 @Getter
13 -public enum TradeOrderRefundStatusEnum { 13 +public enum TradeOrderAfterSaleStatusEnum {
14 14
15 NONE(0, "未退款"), 15 NONE(0, "未退款"),
16 PART(1, "部分退款"), 16 PART(1, "部分退款"),
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java
@@ -13,9 +13,9 @@ import lombok.RequiredArgsConstructor; @@ -13,9 +13,9 @@ import lombok.RequiredArgsConstructor;
13 public enum TradeOrderCancelTypeEnum { 13 public enum TradeOrderCancelTypeEnum {
14 14
15 PAY_TIMEOUT(10, "超时未支付"), 15 PAY_TIMEOUT(10, "超时未支付"),
16 - REFUND_CLOSE(20, "退款关闭"), 16 + AFTER_SALE_CLOSE(20, "退款关闭"),
17 MEMBER_CANCEL(30, "买家取消"), 17 MEMBER_CANCEL(30, "买家取消"),
18 - PAY_ON_DELIVERY(40, "已通过货到付款交易"),; 18 + PAY_ON_DELIVERY(40, "已通过货到付款交易"),; // TODO 芋艿:这个类型,是不是可以去掉
19 19
20 /** 20 /**
21 * 关闭类型 21 * 关闭类型
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/TradeAfterSaleController.java
1 package cn.iocoder.yudao.module.trade.controller.admin.aftersale; 1 package cn.iocoder.yudao.module.trade.controller.admin.aftersale;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleAuditReqVO;  
5 -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleConfirmReqVO; 4 +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
6 import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService; 5 import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService;
7 import io.swagger.annotations.Api; 6 import io.swagger.annotations.Api;
8 import io.swagger.annotations.ApiImplicitParam; 7 import io.swagger.annotations.ApiImplicitParam;
@@ -13,7 +12,6 @@ import org.springframework.validation.annotation.Validated; @@ -13,7 +12,6 @@ import org.springframework.validation.annotation.Validated;
13 import org.springframework.web.bind.annotation.*; 12 import org.springframework.web.bind.annotation.*;
14 13
15 import javax.annotation.Resource; 14 import javax.annotation.Resource;
16 -import javax.annotation.security.PermitAll;  
17 15
18 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 16 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
19 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; 17 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -29,28 +27,38 @@ public class TradeAfterSaleController { @@ -29,28 +27,38 @@ public class TradeAfterSaleController {
29 @Resource 27 @Resource
30 private TradeAfterSaleService afterSaleService; 28 private TradeAfterSaleService afterSaleService;
31 29
32 - @PutMapping("/audit")  
33 - @ApiOperation("审批售后")  
34 - @PreAuthorize("@ss.hasPermission('trade:after-sale:audit')")  
35 - public CommonResult<Boolean> auditAfterSale(@RequestBody TradeAfterSaleAuditReqVO auditReqVO) {  
36 - afterSaleService.auditAfterSale(getLoginUserId(), getClientIP(), auditReqVO); 30 + @PutMapping("/agree")
  31 + @ApiOperation("同意售后")
  32 + @ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
  33 + @PreAuthorize("@ss.hasPermission('trade:after-sale:agree')")
  34 + public CommonResult<Boolean> agreeAfterSale(@RequestParam("id") Long id) {
  35 + afterSaleService.agreeAfterSale(getLoginUserId(), id);
37 return success(true); 36 return success(true);
38 } 37 }
39 38
40 - @PutMapping("/confirm") 39 + @PutMapping("/disagree")
  40 + @ApiOperation("拒绝售后")
  41 + @PreAuthorize("@ss.hasPermission('trade:after-sale:disagree')")
  42 + public CommonResult<Boolean> disagreeAfterSale(@RequestBody TradeAfterSaleDisagreeReqVO confirmReqVO) {
  43 + afterSaleService.disagreeAfterSale(getLoginUserId(), confirmReqVO);
  44 + return success(true);
  45 + }
  46 +
  47 + @PutMapping("/receive")
41 @ApiOperation("确认收货") 48 @ApiOperation("确认收货")
42 - @PreAuthorize("@ss.hasPermission('trade:after-sale:audit')")  
43 - public CommonResult<Boolean> confirmAfterSale(@RequestBody TradeAfterSaleConfirmReqVO confirmReqVO) {  
44 - afterSaleService.confirmAfterSale(getLoginUserId(), getClientIP(), confirmReqVO); 49 + @ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
  50 + @PreAuthorize("@ss.hasPermission('trade:after-sale:receive')")
  51 + public CommonResult<Boolean> receiveAfterSale(@RequestParam("id") Long id) {
  52 + afterSaleService.receiveAfterSale(getLoginUserId(), id);
45 return success(true); 53 return success(true);
46 } 54 }
47 55
48 @PostMapping("/refund") 56 @PostMapping("/refund")
49 - @ApiOperation(value = "确认退款", notes = "提供给【pay】支付服务,退款成功后进行回调")  
50 - @ApiImplicitParam(name = "payRefundId", value = "支付退款编号", required = true, example = "18888")  
51 - @PermitAll  
52 - public CommonResult<Boolean> refundAfterSale(@RequestParam("payRefundId") Long payRefundId) {  
53 - afterSaleService.refundAfterSale(payRefundId); 57 + @ApiOperation(value = "确认退款")
  58 + @ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
  59 + @PreAuthorize("@ss.hasPermission('trade:after-sale:refund')")
  60 + public CommonResult<Boolean> refundAfterSale(@RequestParam("id") Long id) {
  61 + afterSaleService.refundAfterSale(getLoginUserId(), getClientIP(), id);
54 return success(true); 62 return success(true);
55 } 63 }
56 64
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleAuditReqVO.java renamed to yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleDisagreeReqVO.java
@@ -4,22 +4,19 @@ import io.swagger.annotations.ApiModel; @@ -4,22 +4,19 @@ import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
5 import lombok.Data; 5 import lombok.Data;
6 6
  7 +import javax.validation.constraints.NotEmpty;
7 import javax.validation.constraints.NotNull; 8 import javax.validation.constraints.NotNull;
8 9
9 -@ApiModel("管理后台 - 交易售后审批 Request VO") 10 +@ApiModel("管理后台 - 交易售后拒绝 Request VO")
10 @Data 11 @Data
11 -public class TradeAfterSaleAuditReqVO { 12 +public class TradeAfterSaleDisagreeReqVO {
12 13
13 @ApiModelProperty(value = "售后编号", required = true, example = "1024") 14 @ApiModelProperty(value = "售后编号", required = true, example = "1024")
14 @NotNull(message = "售后编号不能为空") 15 @NotNull(message = "售后编号不能为空")
15 private Long id; 16 private Long id;
16 17
17 - @ApiModelProperty(value = "审批结果", required = true, example = "true",  
18 - notes = "true - 通过;false - 不通过")  
19 - @NotNull(message = "审批结果不能为空")  
20 - private Boolean pass;  
21 -  
22 - @ApiModelProperty(value = "审批备注", example = "你猜") 18 + @ApiModelProperty(value = "审批备注", required = true, example = "你猜")
  19 + @NotEmpty(message = "审批备注不能为空")
23 private String auditReason; 20 private String auditReason;
24 21
25 } 22 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleConfirmReqVO.java renamed to yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleRefuseReqVO.java
@@ -6,20 +6,16 @@ import lombok.Data; @@ -6,20 +6,16 @@ import lombok.Data;
6 6
7 import javax.validation.constraints.NotNull; 7 import javax.validation.constraints.NotNull;
8 8
9 -@ApiModel("管理后台 - 交易售后确认收货 Request VO") 9 +@ApiModel("管理后台 - 交易售后拒绝收货 Request VO")
10 @Data 10 @Data
11 -public class TradeAfterSaleConfirmReqVO { 11 +public class TradeAfterSaleRefuseReqVO {
12 12
13 @ApiModelProperty(value = "售后编号", required = true, example = "1024") 13 @ApiModelProperty(value = "售后编号", required = true, example = "1024")
14 @NotNull(message = "售后编号不能为空") 14 @NotNull(message = "售后编号不能为空")
15 private Long id; 15 private Long id;
16 16
17 - @ApiModelProperty(value = "收货结果", required = true, example = "true",  
18 - notes = "true - 确认收货;false - 拒绝收货")  
19 - @NotNull(message = "审批结果不能为空")  
20 - private Boolean pass;  
21 -  
22 - @ApiModelProperty(value = "收货备注", example = "你猜")  
23 - private String receiptMemo; 17 + @ApiModelProperty(value = "收货备注", required = true, example = "你猜")
  18 + @NotNull(message = "收货备注不能为空")
  19 + private String refuseMemo;
24 20
25 } 21 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleDO.java
@@ -86,6 +86,8 @@ public class TradeAfterSaleDO extends BaseDO { @@ -86,6 +86,8 @@ public class TradeAfterSaleDO extends BaseDO {
86 private Long auditUserId; 86 private Long auditUserId;
87 /** 87 /**
88 * 审批备注 88 * 审批备注
  89 + *
  90 + * 注意,只有审批不通过才会填写
89 */ 91 */
90 private String auditReason; 92 private String auditReason;
91 93
@@ -132,8 +134,6 @@ public class TradeAfterSaleDO extends BaseDO { @@ -132,8 +134,6 @@ public class TradeAfterSaleDO extends BaseDO {
132 private Long payRefundId; 134 private Long payRefundId;
133 /** 135 /**
134 * 退款时间 136 * 退款时间
135 - *  
136 - * 退款成功后,才记录该时间  
137 */ 137 */
138 private LocalDateTime refundTime; 138 private LocalDateTime refundTime;
139 139
@@ -155,10 +155,12 @@ public class TradeAfterSaleDO extends BaseDO { @@ -155,10 +155,12 @@ public class TradeAfterSaleDO extends BaseDO {
155 /** 155 /**
156 * 收货时间 156 * 收货时间
157 */ 157 */
158 - private LocalDateTime receiptTime; 158 + private LocalDateTime receiveTime;
159 /** 159 /**
160 * 收货备注 160 * 收货备注
  161 + *
  162 + * 注意,只有拒绝收货才会填写
161 */ 163 */
162 - private String receiptMemo; 164 + private String receiveReason;
163 165
164 } 166 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.TerminalEnum; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO.OrderItem; 5 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO.OrderItem;
6 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; 6 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
7 -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; 7 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderAfterSaleStatusEnum;
8 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; 8 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
10 import com.baomidou.mybatisplus.annotation.KeySequence; 10 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -219,11 +219,11 @@ public class TradeOrderDO extends BaseDO { @@ -219,11 +219,11 @@ public class TradeOrderDO extends BaseDO {
219 219
220 // ========== 退款基本信息 ========== 220 // ========== 退款基本信息 ==========
221 /** 221 /**
222 - * 退款状态 222 + * 收货状态
223 * 223 *
224 - * 枚举 {@link TradeOrderRefundStatusEnum} 224 + * 枚举 {@link TradeOrderAfterSaleStatusEnum}
225 */ 225 */
226 - private Integer refundStatus; 226 + private Integer afterSaleStatus;
227 /** 227 /**
228 * 退款金额,单位:分 228 * 退款金额,单位:分
229 * 229 *
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java
@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; @@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
5 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 5 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
6 import org.apache.ibatis.annotations.Mapper; 6 import org.apache.ibatis.annotations.Mapper;
7 7
  8 +import java.util.List;
  9 +
8 @Mapper 10 @Mapper
9 public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { 11 public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
10 12
@@ -13,4 +15,8 @@ public interface TradeOrderItemMapper extends BaseMapperX&lt;TradeOrderItemDO&gt; { @@ -13,4 +15,8 @@ public interface TradeOrderItemMapper extends BaseMapperX&lt;TradeOrderItemDO&gt; {
13 new LambdaUpdateWrapper<>(new TradeOrderItemDO().setId(id).setAfterSaleStatus(oldAfterSaleStatus))); 15 new LambdaUpdateWrapper<>(new TradeOrderItemDO().setId(id).setAfterSaleStatus(oldAfterSaleStatus)));
14 } 16 }
15 17
  18 + default List<TradeOrderItemDO> selectListByOrderId(Long orderId) {
  19 + return selectList(TradeOrderItemDO::getOrderId, orderId);
  20 + }
  21 +
16 } 22 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleService.java
1 package cn.iocoder.yudao.module.trade.service.aftersale; 1 package cn.iocoder.yudao.module.trade.service.aftersale;
2 2
3 -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleAuditReqVO;  
4 -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleConfirmReqVO; 3 +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRefuseReqVO;
5 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO; 5 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
6 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO; 6 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
7 7
@@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSa @@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSa
13 public interface TradeAfterSaleService { 13 public interface TradeAfterSaleService {
14 14
15 /** 15 /**
16 - * 创建交易售后 16 + * 【会员】创建交易售后
17 * <p> 17 * <p>
18 * 一般是用户发起售后请求 18 * 一般是用户发起售后请求
19 * 19 *
@@ -24,16 +24,23 @@ public interface TradeAfterSaleService { @@ -24,16 +24,23 @@ public interface TradeAfterSaleService {
24 Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO); 24 Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO);
25 25
26 /** 26 /**
27 - * 审批交易售后 27 + * 【管理员】同意交易售后
  28 + *
  29 + * @param userId 管理员用户编号
  30 + * @param id 交易售后编号
  31 + */
  32 + void agreeAfterSale(Long userId, Long id);
  33 +
  34 + /**
  35 + * 【管理员】拒绝交易售后
28 * 36 *
29 * @param userId 管理员用户编号 37 * @param userId 管理员用户编号
30 - * @param userIp 操作 IP  
31 * @param auditReqVO 审批 Request 信息 38 * @param auditReqVO 审批 Request 信息
32 */ 39 */
33 - void auditAfterSale(Long userId, String userIp, TradeAfterSaleAuditReqVO auditReqVO); 40 + void disagreeAfterSale(Long userId, TradeAfterSaleDisagreeReqVO auditReqVO);
34 41
35 /** 42 /**
36 - * 退回货物 43 + * 【会员】退回货物
37 * 44 *
38 * @param userId 会员用户编号 45 * @param userId 会员用户编号
39 * @param deliveryReqVO 退货 Request 信息 46 * @param deliveryReqVO 退货 Request 信息
@@ -41,19 +48,28 @@ public interface TradeAfterSaleService { @@ -41,19 +48,28 @@ public interface TradeAfterSaleService {
41 void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO); 48 void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO);
42 49
43 /** 50 /**
44 - * 确认收货 51 + * 【管理员】确认收货
  52 + *
  53 + * @param userId 管理员编号
  54 + * @param id 交易售后编号
  55 + */
  56 + void receiveAfterSale(Long userId, Long id);
  57 +
  58 + /**
  59 + * 【管理员】拒绝收货
45 * 60 *
46 * @param userId 管理员用户编号 61 * @param userId 管理员用户编号
47 - * @param userIp 操作 IP  
48 * @param confirmReqVO 收货 Request 信息 62 * @param confirmReqVO 收货 Request 信息
49 */ 63 */
50 - void confirmAfterSale(Long userId, String userIp, TradeAfterSaleConfirmReqVO confirmReqVO); 64 + void refuseAfterSale(Long userId, TradeAfterSaleRefuseReqVO confirmReqVO);
51 65
52 /** 66 /**
53 - * 确认退款,由【pay】支付服务回调 67 + * 【管理员】确认退款
54 * 68 *
55 - * @param payRefundId 支付退款编号 69 + * @param userId 管理员用户编号
  70 + * @param userIp 管理员用户 IP
  71 + * @param id 售后编号
56 */ 72 */
57 - void refundAfterSale(Long payRefundId); 73 + void refundAfterSale(Long userId, String userIp, Long id);
58 74
59 } 75 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
@@ -4,10 +4,8 @@ import cn.hutool.core.util.ObjectUtil; @@ -4,10 +4,8 @@ import cn.hutool.core.util.ObjectUtil;
4 import cn.hutool.core.util.RandomUtil; 4 import cn.hutool.core.util.RandomUtil;
5 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; 5 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
6 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; 6 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
7 -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;  
8 -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;  
9 -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleAuditReqVO;  
10 -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleConfirmReqVO; 7 +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRefuseReqVO;
11 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO; 9 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
12 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO; 10 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
13 import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert; 11 import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert;
@@ -28,7 +26,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager @@ -28,7 +26,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
28 import org.springframework.validation.annotation.Validated; 26 import org.springframework.validation.annotation.Validated;
29 27
30 import javax.annotation.Resource; 28 import javax.annotation.Resource;
31 -  
32 import java.time.LocalDateTime; 29 import java.time.LocalDateTime;
33 30
34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -66,6 +63,13 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { @@ -66,6 +63,13 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
66 return afterSale.getId(); 63 return afterSale.getId();
67 } 64 }
68 65
  66 + /**
  67 + * 校验交易订单项是否可以申请售后
  68 + *
  69 + * @param userId 用户编号
  70 + * @param createReqVO 售后创建信息
  71 + * @return 交易订单项
  72 + */
69 private TradeOrderItemDO validateOrderItemApplicable(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) { 73 private TradeOrderItemDO validateOrderItemApplicable(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) {
70 // 校验订单项存在 74 // 校验订单项存在
71 TradeOrderItemDO orderItem = tradeOrderService.getOrderItem(userId, createReqVO.getOrderItemId()); 75 TradeOrderItemDO orderItem = tradeOrderService.getOrderItem(userId, createReqVO.getOrderItemId());
@@ -116,7 +120,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { @@ -116,7 +120,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
116 120
117 // 更新交易订单项的售后状态 121 // 更新交易订单项的售后状态
118 tradeOrderService.updateOrderItemAfterSaleStatus(tradeOrderItem.getId(), 122 tradeOrderService.updateOrderItemAfterSaleStatus(tradeOrderItem.getId(),
119 - TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus()); 123 + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
  124 + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), null);
120 125
121 // TODO 记录售后日志 126 // TODO 记录售后日志
122 127
@@ -125,76 +130,61 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { @@ -125,76 +130,61 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
125 } 130 }
126 131
127 @Override 132 @Override
128 - @Transactional  
129 - public void auditAfterSale(Long userId, String userIp,  
130 - TradeAfterSaleAuditReqVO auditReqVO) { 133 + @Transactional(rollbackFor = Exception.class)
  134 + public void agreeAfterSale(Long userId, Long id) {
131 // 校验售后单存在,并状态未审批 135 // 校验售后单存在,并状态未审批
132 - TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(auditReqVO.getId());  
133 - if (afterSale == null) {  
134 - throw exception(AFTER_SALE_NOT_FOUND);  
135 - }  
136 - if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus())) {  
137 - throw exception(AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY);  
138 - }  
139 -  
140 - // 进行审批  
141 - if (auditReqVO.getPass()) {  
142 - auditAfterSalePass(userId, userIp, auditReqVO, afterSale);  
143 - } else {  
144 - auditAfterSaleReject(userId, auditReqVO, afterSale);  
145 - }  
146 - } 136 + TradeAfterSaleDO afterSale = validateAfterSaleAuditable(id);
147 137
148 - private void auditAfterSalePass(Long userId, String userIp,  
149 - TradeAfterSaleAuditReqVO auditReqVO, TradeAfterSaleDO afterSale) {  
150 // 更新售后单的状态 138 // 更新售后单的状态
151 // 情况一:退款:标记为 WAIT_REFUND 状态。后续等退款发起成功后,在标记为 COMPLETE 状态 139 // 情况一:退款:标记为 WAIT_REFUND 状态。后续等退款发起成功后,在标记为 COMPLETE 状态
152 // 情况二:退货退款:需要等用户退货后,才能发起退款 140 // 情况二:退货退款:需要等用户退货后,才能发起退款
153 Integer newStatus = afterSale.getType().equals(TradeAfterSaleTypeEnum.REFUND.getType()) ? 141 Integer newStatus = afterSale.getType().equals(TradeAfterSaleTypeEnum.REFUND.getType()) ?
154 - TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus() : TradeAfterSaleStatusEnum.SELLER_PASS.getStatus(); 142 + TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus() : TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus();
155 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO() 143 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO()
156 - .setStatus(newStatus).setAuditUserId(userId)  
157 - .setAuditReason(auditReqVO.getAuditReason()).setAuditTime(LocalDateTime.now()));  
158 -  
159 - // 如果直接退款,则发起售后退款  
160 - if (afterSale.getType().equals(TradeAfterSaleTypeEnum.REFUND.getType())) {  
161 - createPayRefund(userIp, afterSale);  
162 - } 144 + .setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now()));
163 145
164 // TODO 记录售后日志 146 // TODO 记录售后日志
165 147
166 // TODO 发送售后消息 148 // TODO 发送售后消息
167 } 149 }
168 150
169 - private void auditAfterSaleReject(Long userId,  
170 - TradeAfterSaleAuditReqVO auditReqVO, TradeAfterSaleDO afterSale) {  
171 - // 更新售后单的状态  
172 - Integer newStatus = TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus();  
173 - updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO()  
174 - .setStatus(newStatus).setAuditUserId(userId)  
175 - .setAuditReason(auditReqVO.getAuditReason()).setAuditTime(LocalDateTime.now())); 151 + @Override
  152 + @Transactional(rollbackFor = Exception.class)
  153 + public void disagreeAfterSale(Long userId, TradeAfterSaleDisagreeReqVO auditReqVO) {
  154 + // 校验售后单存在,并状态未审批
  155 + TradeAfterSaleDO afterSale = validateAfterSaleAuditable(auditReqVO.getId());
176 156
177 - // 更新交易订单项的售后状态为【未申请】  
178 - tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),  
179 - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); 157 + // 更新售后单的状态
  158 + Integer newStatus = TradeAfterSaleStatusEnum.SELLER_DISAGREE.getStatus();
  159 + updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO()
  160 + .setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now())
  161 + .setAuditReason(auditReqVO.getAuditReason()));
180 162
181 // TODO 记录售后日志 163 // TODO 记录售后日志
182 164
183 // TODO 发送售后消息 165 // TODO 发送售后消息
184 - }  
185 166
186 - private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) {  
187 - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { 167 + // 更新交易订单项的售后状态为【未申请】
  168 + tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
  169 + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
  170 + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
  171 + }
188 172
189 - @Override  
190 - public void afterCommit() {  
191 - // 创建退款单  
192 - PayRefundCreateReqDTO createReqDTO = TradeAfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties);  
193 - Long payRefundId = payRefundApi.createPayRefund(createReqDTO);  
194 - // 更新售后单的退款单号  
195 - tradeAfterSaleMapper.updateById(new TradeAfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));  
196 - }  
197 - }); 173 + /**
  174 + * 校验售后单是否可审批(同意售后、拒绝售后)
  175 + *
  176 + * @param id 售后编号
  177 + * @return 售后单
  178 + */
  179 + private TradeAfterSaleDO validateAfterSaleAuditable(Long id) {
  180 + TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
  181 + if (afterSale == null) {
  182 + throw exception(AFTER_SALE_NOT_FOUND);
  183 + }
  184 + if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus())) {
  185 + throw exception(AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY);
  186 + }
  187 + return afterSale;
198 } 188 }
199 189
200 private void updateAfterSaleStatus(Long id, Integer status, TradeAfterSaleDO updateObj) { 190 private void updateAfterSaleStatus(Long id, Integer status, TradeAfterSaleDO updateObj) {
@@ -205,19 +195,20 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { @@ -205,19 +195,20 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
205 } 195 }
206 196
207 @Override 197 @Override
  198 + @Transactional(rollbackFor = Exception.class)
208 public void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO) { 199 public void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO) {
209 // 校验售后单存在,并状态未退货 200 // 校验售后单存在,并状态未退货
210 TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(deliveryReqVO.getId()); 201 TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(deliveryReqVO.getId());
211 if (afterSale == null) { 202 if (afterSale == null) {
212 throw exception(AFTER_SALE_NOT_FOUND); 203 throw exception(AFTER_SALE_NOT_FOUND);
213 } 204 }
214 - if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_PASS.getStatus())) {  
215 - throw exception(AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_PASS); 205 + if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus())) {
  206 + throw exception(AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE);
216 } 207 }
217 208
218 // 更新售后单的物流信息 209 // 更新售后单的物流信息
219 - updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.SELLER_PASS.getStatus(), new TradeAfterSaleDO()  
220 - .setStatus(TradeAfterSaleStatusEnum.BUYER_RETURN.getStatus()) 210 + updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus(), new TradeAfterSaleDO()
  211 + .setStatus(TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus())
221 .setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo()) 212 .setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo())
222 .setDeliveryTime(deliveryReqVO.getDeliveryTime())); 213 .setDeliveryTime(deliveryReqVO.getDeliveryTime()));
223 214
@@ -227,64 +218,69 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { @@ -227,64 +218,69 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
227 } 218 }
228 219
229 @Override 220 @Override
230 - public void confirmAfterSale(Long userId, String userIp,  
231 - TradeAfterSaleConfirmReqVO confirmReqVO) {  
232 - // 校验售后单存在,并状态未审批 221 + @Transactional(rollbackFor = Exception.class)
  222 + public void receiveAfterSale(Long userId, Long id) {
  223 + // 校验售后单存在,并状态为已退货
  224 + TradeAfterSaleDO afterSale = validateAfterSaleReceivable(id);
  225 +
  226 + // 更新售后单的状态
  227 + updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new TradeAfterSaleDO()
  228 + .setStatus(TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus()).setReceiveTime(LocalDateTime.now()));
  229 +
  230 + // TODO 记录售后日志
  231 +
  232 + // TODO 发送售后消息
  233 + }
  234 +
  235 + @Override
  236 + @Transactional(rollbackFor = Exception.class)
  237 + public void refuseAfterSale(Long userId, TradeAfterSaleRefuseReqVO confirmReqVO) {
  238 + // 校验售后单存在,并状态为已退货
233 TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(confirmReqVO.getId()); 239 TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(confirmReqVO.getId());
234 if (afterSale == null) { 240 if (afterSale == null) {
235 throw exception(AFTER_SALE_NOT_FOUND); 241 throw exception(AFTER_SALE_NOT_FOUND);
236 } 242 }
237 - if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_RETURN.getStatus())) {  
238 - throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_RETURN);  
239 - }  
240 -  
241 - // 进行审批  
242 - if (confirmReqVO.getPass()) {  
243 - confirmAfterSalePass(userId, userIp, confirmReqVO, afterSale);  
244 - } else {  
245 - confirmAfterSaleReject(userId, confirmReqVO, afterSale); 243 + if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) {
  244 + throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY);
246 } 245 }
247 - }  
248 246
249 - private void confirmAfterSalePass(Long userId, String userIp,  
250 - TradeAfterSaleConfirmReqVO confirmReqVO, TradeAfterSaleDO afterSale) {  
251 // 更新售后单的状态 247 // 更新售后单的状态
252 - Integer newStatus = TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus();  
253 - updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_RETURN.getStatus(), new TradeAfterSaleDO()  
254 - .setStatus(newStatus).setReceiptTime(LocalDateTime.now()).setReceiptMemo(confirmReqVO.getReceiptMemo()));  
255 -  
256 - // 如果直接退款,则发起售后退款  
257 - if (afterSale.getType().equals(TradeAfterSaleTypeEnum.REFUND.getType())) {  
258 - createPayRefund(userIp, afterSale);  
259 - } 248 + updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new TradeAfterSaleDO()
  249 + .setStatus(TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus()).setReceiveTime(LocalDateTime.now())
  250 + .setReceiveReason(confirmReqVO.getRefuseMemo()));
260 251
261 // TODO 记录售后日志 252 // TODO 记录售后日志
262 253
263 // TODO 发送售后消息 254 // TODO 发送售后消息
264 - }  
265 -  
266 - private void confirmAfterSaleReject(Long userId, TradeAfterSaleConfirmReqVO confirmReqVO, TradeAfterSaleDO afterSale) {  
267 - // 更新售后单的状态  
268 - Integer newStatus = TradeAfterSaleStatusEnum.SELLER_TERMINATION.getStatus();  
269 - updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_RETURN.getStatus(), new TradeAfterSaleDO()  
270 - .setStatus(newStatus).setReceiptTime(LocalDateTime.now()).setReceiptMemo(confirmReqVO.getReceiptMemo()));  
271 255
272 // 更新交易订单项的售后状态为【未申请】 256 // 更新交易订单项的售后状态为【未申请】
273 tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(), 257 tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
274 - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());  
275 -  
276 - // TODO 记录售后日志 258 + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
  259 + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
  260 + }
277 261
278 - // TODO 发送售后消息 262 + /**
  263 + * 校验售后单是否可收货,即处于买家已发货
  264 + *
  265 + * @param id 售后编号
  266 + * @return 售后单
  267 + */
  268 + private TradeAfterSaleDO validateAfterSaleReceivable(Long id) {
  269 + TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
  270 + if (afterSale == null) {
  271 + throw exception(AFTER_SALE_NOT_FOUND);
  272 + }
  273 + if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) {
  274 + throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY);
  275 + }
  276 + return afterSale;
279 } 277 }
280 278
281 @Override 279 @Override
282 - public void refundAfterSale(Long payRefundId) {  
283 - // 校验退款单  
284 - PayRefundRespDTO payRefund = validatePayRefundSuccess(payRefundId);  
285 - 280 + @Transactional(rollbackFor = Exception.class)
  281 + public void refundAfterSale(Long userId, String userIp, Long id) {
286 // 校验售后单的状态,并状态待退款 282 // 校验售后单的状态,并状态待退款
287 - TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectByPayRefundId(payRefundId); 283 + TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectByPayRefundId(id);
288 if (afterSale == null) { 284 if (afterSale == null) {
289 throw exception(AFTER_SALE_NOT_FOUND); 285 throw exception(AFTER_SALE_NOT_FOUND);
290 } 286 }
@@ -292,28 +288,35 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { @@ -292,28 +288,35 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
292 throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND); 288 throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND);
293 } 289 }
294 290
  291 + // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起
  292 + createPayRefund(userIp, afterSale);
  293 +
295 // 更新售后单的状态为【已完成】 294 // 更新售后单的状态为【已完成】
296 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus(), new TradeAfterSaleDO() 295 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus(), new TradeAfterSaleDO()
297 - .setStatus(TradeAfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(payRefund.getSuccessTime()));  
298 -  
299 - // 更新交易订单项的售后状态为【已完成】  
300 - tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),  
301 - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus()); 296 + .setStatus(TradeAfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
302 297
303 // TODO 记录售后日志 298 // TODO 记录售后日志
304 299
305 // TODO 发送售后消息 300 // TODO 发送售后消息
  301 +
  302 + // 更新交易订单项的售后状态为【已完成】
  303 + tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
  304 + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
  305 + TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), afterSale.getRefundPrice());
306 } 306 }
307 307
308 - private PayRefundRespDTO validatePayRefundSuccess(Long payRefundId) {  
309 - PayRefundRespDTO payRefund = payRefundApi.getPayRefund(payRefundId);  
310 - if (payRefund == null) {  
311 - throw exception(AFTER_SALE_REFUND_FAIL_PAY_REFUND_NOT_FOUND);  
312 - }  
313 - if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {  
314 - throw exception(AFTER_SALE_REFUND_FAIL_PAY_REFUND_STATUS_NOT_SUCCESS);  
315 - }  
316 - return payRefund; 308 + private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) {
  309 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  310 +
  311 + @Override
  312 + public void afterCommit() {
  313 + // 创建退款单
  314 + PayRefundCreateReqDTO createReqDTO = TradeAfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties);
  315 + Long payRefundId = payRefundApi.createPayRefund(createReqDTO);
  316 + // 更新售后单的退款单号
  317 + tradeAfterSaleMapper.updateById(new TradeAfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
  318 + }
  319 + });
317 } 320 }
318 321
319 } 322 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java
@@ -50,6 +50,8 @@ public interface TradeOrderService { @@ -50,6 +50,8 @@ public interface TradeOrderService {
50 * @param id 交易订单项编号 50 * @param id 交易订单项编号
51 * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常 51 * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
52 * @param newAfterSaleStatus 目标售后状态 52 * @param newAfterSaleStatus 目标售后状态
  53 + * @param refundPrice 退款金额;当订单项退款成功时,必须传递该值
53 */ 54 */
54 - void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus); 55 + void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus,
  56 + Integer newAfterSaleStatus, Integer refundPrice);
55 } 57 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.order; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.order;
2 2
3 import cn.hutool.core.util.IdUtil; 3 import cn.hutool.core.util.IdUtil;
4 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.enums.TerminalEnum; 7 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 8 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -24,17 +25,16 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreate @@ -24,17 +25,16 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreate
24 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; 25 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
25 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; 26 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
26 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 27 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
27 -import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;  
28 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; 28 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
  29 +import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
29 import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants; 30 import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
30 -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;  
31 -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;  
32 -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 31 +import cn.iocoder.yudao.module.trade.enums.order.*;
33 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; 32 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
34 import org.springframework.stereotype.Service; 33 import org.springframework.stereotype.Service;
35 import org.springframework.transaction.annotation.Transactional; 34 import org.springframework.transaction.annotation.Transactional;
36 35
37 import javax.annotation.Resource; 36 import javax.annotation.Resource;
  37 +import java.time.LocalDateTime;
38 import java.util.List; 38 import java.util.List;
39 import java.util.Map; 39 import java.util.Map;
40 import java.util.Objects; 40 import java.util.Objects;
@@ -180,12 +180,12 @@ public class TradeOrderServiceImpl implements TradeOrderService { @@ -180,12 +180,12 @@ public class TradeOrderServiceImpl implements TradeOrderService {
180 tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的; 180 tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
181 tradeOrderDO.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); 181 tradeOrderDO.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
182 tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType()); 182 tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
183 - tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); 183 + tradeOrderDO.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.NONE.getStatus());
184 tradeOrderDO.setProductCount(getSumValue(order.getItems(), PriceCalculateRespDTO.OrderItem::getCount, Integer::sum)); 184 tradeOrderDO.setProductCount(getSumValue(order.getItems(), PriceCalculateRespDTO.OrderItem::getCount, Integer::sum));
185 tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源? 185 tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
186 tradeOrderDO.setAdjustPrice(0).setPayed(false); // 支付信息 186 tradeOrderDO.setAdjustPrice(0).setPayed(false); // 支付信息
187 tradeOrderDO.setDeliveryStatus(false); // 物流信息 187 tradeOrderDO.setDeliveryStatus(false); // 物流信息
188 - tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息 188 + tradeOrderDO.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息
189 tradeOrderMapper.insert(tradeOrderDO); 189 tradeOrderMapper.insert(tradeOrderDO);
190 return tradeOrderDO; 190 return tradeOrderDO;
191 } 191 }
@@ -252,11 +252,52 @@ public class TradeOrderServiceImpl implements TradeOrderService { @@ -252,11 +252,52 @@ public class TradeOrderServiceImpl implements TradeOrderService {
252 } 252 }
253 253
254 @Override 254 @Override
255 - public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus) { 255 + public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, Integer refundPrice) {
  256 + // 如果退款成功,则 refundPrice 非空
  257 + if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())
  258 + && refundPrice == null) {
  259 + throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
  260 + }
  261 +
  262 + // 更新订单项
256 int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus); 263 int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus);
257 if (updateCount <= 0) { 264 if (updateCount <= 0) {
258 throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL); 265 throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
259 } 266 }
  267 +
  268 + // 如果有退款金额,则需要更新订单
  269 + if (refundPrice == null) {
  270 + return;
  271 + }
  272 + // 计算总的退款金额
  273 + TradeOrderDO order = tradeOrderMapper.selectById(tradeOrderItemMapper.selectById(id).getOrderId());
  274 + Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
  275 + if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
  276 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
  277 + .setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice)
  278 + .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
  279 +
  280 + // TODO 芋艿:记录订单日志
  281 +
  282 + // TODO 芋艿:站内信?
  283 + } else { // 如果部分售后,则更新退款金额
  284 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
  285 + .setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
  286 + }
  287 +
  288 + // TODO 芋艿:未来如果有分佣,需要更新相关分佣订单为已失效
  289 + }
  290 +
  291 + /**
  292 + * 判断指定订单的所有订单项,是不是都售后成功
  293 + *
  294 + * @param id 订单编号
  295 + * @return 是否都售后成功
  296 + */
  297 + private boolean isAllOrderItemAfterSaleSuccess(Long id) {
  298 + List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
  299 + return orderItems.stream().allMatch(orderItem -> Objects.equals(orderItem.getAfterSaleStatus(),
  300 + TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus()));
260 } 301 }
261 302
262 } 303 }
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java
@@ -21,7 +21,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; @@ -21,7 +21,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
21 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; 21 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
22 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; 22 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
23 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; 23 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
24 -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; 24 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderAfterSaleStatusEnum;
25 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; 25 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
26 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 26 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
27 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; 27 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig;
@@ -182,7 +182,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest { @@ -182,7 +182,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
182 assertEquals(tradeOrderDO.getReceiverAreaId(), 3306L); 182 assertEquals(tradeOrderDO.getReceiverAreaId(), 3306L);
183 assertEquals(tradeOrderDO.getReceiverPostCode(), 85757); 183 assertEquals(tradeOrderDO.getReceiverPostCode(), 85757);
184 assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村"); 184 assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村");
185 - assertEquals(tradeOrderDO.getRefundStatus(), TradeOrderRefundStatusEnum.NONE.getStatus()); 185 + assertEquals(tradeOrderDO.getAfterSaleStatus(), TradeOrderAfterSaleStatusEnum.NONE.getStatus());
186 assertEquals(tradeOrderDO.getRefundPrice(), 0); 186 assertEquals(tradeOrderDO.getRefundPrice(), 0);
187 assertEquals(tradeOrderDO.getCouponPrice(), 30); 187 assertEquals(tradeOrderDO.getCouponPrice(), 30);
188 assertEquals(tradeOrderDO.getPointPrice(), 10); 188 assertEquals(tradeOrderDO.getPointPrice(), 10);