Commit cd2bc112cc4a12e0172beaa99f4ed6224d391669
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 | 32 | ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1011000105, "订单项已申请售后,无法重复申请"); |
| 33 | 33 | ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1011000106, "审批失败,售后状态不处于审批中"); |
| 34 | 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 | 39 | // ========== Cart 模块 1-011-001-000 ========== |
| 42 | 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 | 15 | public enum TradeAfterSaleStatusEnum { |
| 16 | 16 | |
| 17 | 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 | 21 | COMPLETE(50, "完成"), // 完成退款 |
| 22 | 22 | |
| 23 | 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 | 4 | import lombok.RequiredArgsConstructor; |
| 5 | 5 | |
| 6 | 6 | /** |
| 7 | - * 交易订单 - 退款状态 | |
| 7 | + * 交易订单 - 售后状态 | |
| 8 | 8 | * |
| 9 | 9 | * @author Sin |
| 10 | 10 | */ |
| 11 | 11 | @RequiredArgsConstructor |
| 12 | 12 | @Getter |
| 13 | -public enum TradeOrderRefundStatusEnum { | |
| 13 | +public enum TradeOrderAfterSaleStatusEnum { | |
| 14 | 14 | |
| 15 | 15 | NONE(0, "未退款"), |
| 16 | 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 | 13 | public enum TradeOrderCancelTypeEnum { |
| 14 | 14 | |
| 15 | 15 | PAY_TIMEOUT(10, "超时未支付"), |
| 16 | - REFUND_CLOSE(20, "退款关闭"), | |
| 16 | + AFTER_SALE_CLOSE(20, "退款关闭"), | |
| 17 | 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 | 1 | package cn.iocoder.yudao.module.trade.controller.admin.aftersale; |
| 2 | 2 | |
| 3 | 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 | 5 | import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService; |
| 7 | 6 | import io.swagger.annotations.Api; |
| 8 | 7 | import io.swagger.annotations.ApiImplicitParam; |
| ... | ... | @@ -13,7 +12,6 @@ import org.springframework.validation.annotation.Validated; |
| 13 | 12 | import org.springframework.web.bind.annotation.*; |
| 14 | 13 | |
| 15 | 14 | import javax.annotation.Resource; |
| 16 | -import javax.annotation.security.PermitAll; | |
| 17 | 15 | |
| 18 | 16 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 19 | 17 | import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; |
| ... | ... | @@ -29,28 +27,38 @@ public class TradeAfterSaleController { |
| 29 | 27 | @Resource |
| 30 | 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 | 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 | 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 | 53 | return success(true); |
| 46 | 54 | } |
| 47 | 55 | |
| 48 | 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 | 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 | 4 | import io.swagger.annotations.ApiModelProperty; |
| 5 | 5 | import lombok.Data; |
| 6 | 6 | |
| 7 | +import javax.validation.constraints.NotEmpty; | |
| 7 | 8 | import javax.validation.constraints.NotNull; |
| 8 | 9 | |
| 9 | -@ApiModel("管理后台 - 交易售后审批 Request VO") | |
| 10 | +@ApiModel("管理后台 - 交易售后拒绝 Request VO") | |
| 10 | 11 | @Data |
| 11 | -public class TradeAfterSaleAuditReqVO { | |
| 12 | +public class TradeAfterSaleDisagreeReqVO { | |
| 12 | 13 | |
| 13 | 14 | @ApiModelProperty(value = "售后编号", required = true, example = "1024") |
| 14 | 15 | @NotNull(message = "售后编号不能为空") |
| 15 | 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 | 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 | 6 | |
| 7 | 7 | import javax.validation.constraints.NotNull; |
| 8 | 8 | |
| 9 | -@ApiModel("管理后台 - 交易售后确认收货 Request VO") | |
| 9 | +@ApiModel("管理后台 - 交易售后拒绝收货 Request VO") | |
| 10 | 10 | @Data |
| 11 | -public class TradeAfterSaleConfirmReqVO { | |
| 11 | +public class TradeAfterSaleRefuseReqVO { | |
| 12 | 12 | |
| 13 | 13 | @ApiModelProperty(value = "售后编号", required = true, example = "1024") |
| 14 | 14 | @NotNull(message = "售后编号不能为空") |
| 15 | 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 | 86 | private Long auditUserId; |
| 87 | 87 | /** |
| 88 | 88 | * 审批备注 |
| 89 | + * | |
| 90 | + * 注意,只有审批不通过才会填写 | |
| 89 | 91 | */ |
| 90 | 92 | private String auditReason; |
| 91 | 93 | |
| ... | ... | @@ -132,8 +134,6 @@ public class TradeAfterSaleDO extends BaseDO { |
| 132 | 134 | private Long payRefundId; |
| 133 | 135 | /** |
| 134 | 136 | * 退款时间 |
| 135 | - * | |
| 136 | - * 退款成功后,才记录该时间 | |
| 137 | 137 | */ |
| 138 | 138 | private LocalDateTime refundTime; |
| 139 | 139 | |
| ... | ... | @@ -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 | 4 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 5 | 5 | import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO.OrderItem; |
| 6 | 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 | 8 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; |
| 9 | 9 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; |
| 10 | 10 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| ... | ... | @@ -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 | 5 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 6 | 6 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 7 | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 8 | 10 | @Mapper |
| 9 | 11 | public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { |
| 10 | 12 | |
| ... | ... | @@ -13,4 +15,8 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { |
| 13 | 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 | 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 | 5 | import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO; |
| 6 | 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 | 13 | public interface TradeAfterSaleService { |
| 14 | 14 | |
| 15 | 15 | /** |
| 16 | - * 创建交易售后 | |
| 16 | + * 【会员】创建交易售后 | |
| 17 | 17 | * <p> |
| 18 | 18 | * 一般是用户发起售后请求 |
| 19 | 19 | * |
| ... | ... | @@ -24,16 +24,23 @@ public interface TradeAfterSaleService { |
| 24 | 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 | 37 | * @param userId 管理员用户编号 |
| 30 | - * @param userIp 操作 IP | |
| 31 | 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 | 45 | * @param userId 会员用户编号 |
| 39 | 46 | * @param deliveryReqVO 退货 Request 信息 |
| ... | ... | @@ -41,19 +48,28 @@ public interface TradeAfterSaleService { |
| 41 | 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 | 61 | * @param userId 管理员用户编号 |
| 47 | - * @param userIp 操作 IP | |
| 48 | 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 | 4 | import cn.hutool.core.util.RandomUtil; |
| 5 | 5 | import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; |
| 6 | 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 | 9 | import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO; |
| 12 | 10 | import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO; |
| 13 | 11 | import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert; |
| ... | ... | @@ -28,7 +26,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager |
| 28 | 26 | import org.springframework.validation.annotation.Validated; |
| 29 | 27 | |
| 30 | 28 | import javax.annotation.Resource; |
| 31 | - | |
| 32 | 29 | import java.time.LocalDateTime; |
| 33 | 30 | |
| 34 | 31 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -66,6 +63,13 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { |
| 66 | 63 | return afterSale.getId(); |
| 67 | 64 | } |
| 68 | 65 | |
| 66 | + /** | |
| 67 | + * 校验交易订单项是否可以申请售后 | |
| 68 | + * | |
| 69 | + * @param userId 用户编号 | |
| 70 | + * @param createReqVO 售后创建信息 | |
| 71 | + * @return 交易订单项 | |
| 72 | + */ | |
| 69 | 73 | private TradeOrderItemDO validateOrderItemApplicable(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) { |
| 70 | 74 | // 校验订单项存在 |
| 71 | 75 | TradeOrderItemDO orderItem = tradeOrderService.getOrderItem(userId, createReqVO.getOrderItemId()); |
| ... | ... | @@ -116,7 +120,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { |
| 116 | 120 | |
| 117 | 121 | // 更新交易订单项的售后状态 |
| 118 | 122 | tradeOrderService.updateOrderItemAfterSaleStatus(tradeOrderItem.getId(), |
| 119 | - TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus()); | |
| 123 | + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), | |
| 124 | + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), null); | |
| 120 | 125 | |
| 121 | 126 | // TODO 记录售后日志 |
| 122 | 127 | |
| ... | ... | @@ -125,76 +130,61 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { |
| 125 | 130 | } |
| 126 | 131 | |
| 127 | 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 | 139 | // 情况一:退款:标记为 WAIT_REFUND 状态。后续等退款发起成功后,在标记为 COMPLETE 状态 |
| 152 | 140 | // 情况二:退货退款:需要等用户退货后,才能发起退款 |
| 153 | 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 | 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 | 146 | // TODO 记录售后日志 |
| 165 | 147 | |
| 166 | 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 | 163 | // TODO 记录售后日志 |
| 182 | 164 | |
| 183 | 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 | 190 | private void updateAfterSaleStatus(Long id, Integer status, TradeAfterSaleDO updateObj) { |
| ... | ... | @@ -205,19 +195,20 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { |
| 205 | 195 | } |
| 206 | 196 | |
| 207 | 197 | @Override |
| 198 | + @Transactional(rollbackFor = Exception.class) | |
| 208 | 199 | public void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO) { |
| 209 | 200 | // 校验售后单存在,并状态未退货 |
| 210 | 201 | TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(deliveryReqVO.getId()); |
| 211 | 202 | if (afterSale == null) { |
| 212 | 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 | 212 | .setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo()) |
| 222 | 213 | .setDeliveryTime(deliveryReqVO.getDeliveryTime())); |
| 223 | 214 | |
| ... | ... | @@ -227,64 +218,69 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { |
| 227 | 218 | } |
| 228 | 219 | |
| 229 | 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 | 239 | TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(confirmReqVO.getId()); |
| 234 | 240 | if (afterSale == null) { |
| 235 | 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 | 252 | // TODO 记录售后日志 |
| 262 | 253 | |
| 263 | 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 | 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 | 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 | 284 | if (afterSale == null) { |
| 289 | 285 | throw exception(AFTER_SALE_NOT_FOUND); |
| 290 | 286 | } |
| ... | ... | @@ -292,28 +288,35 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService { |
| 292 | 288 | throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND); |
| 293 | 289 | } |
| 294 | 290 | |
| 291 | + // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起 | |
| 292 | + createPayRefund(userIp, afterSale); | |
| 293 | + | |
| 295 | 294 | // 更新售后单的状态为【已完成】 |
| 296 | 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 | 298 | // TODO 记录售后日志 |
| 304 | 299 | |
| 305 | 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 | 50 | * @param id 交易订单项编号 |
| 51 | 51 | * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常 |
| 52 | 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 | 2 | |
| 3 | 3 | import cn.hutool.core.util.IdUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjectUtil; |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 5 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 7 | import cn.iocoder.yudao.framework.common.enums.TerminalEnum; |
| 7 | 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 | 25 | import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; |
| 25 | 26 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; |
| 26 | 27 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 27 | -import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; | |
| 28 | 28 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; |
| 29 | +import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; | |
| 29 | 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 | 32 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; |
| 34 | 33 | import org.springframework.stereotype.Service; |
| 35 | 34 | import org.springframework.transaction.annotation.Transactional; |
| 36 | 35 | |
| 37 | 36 | import javax.annotation.Resource; |
| 37 | +import java.time.LocalDateTime; | |
| 38 | 38 | import java.util.List; |
| 39 | 39 | import java.util.Map; |
| 40 | 40 | import java.util.Objects; |
| ... | ... | @@ -180,12 +180,12 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 180 | 180 | tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的; |
| 181 | 181 | tradeOrderDO.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); |
| 182 | 182 | tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType()); |
| 183 | - tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); | |
| 183 | + tradeOrderDO.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.NONE.getStatus()); | |
| 184 | 184 | tradeOrderDO.setProductCount(getSumValue(order.getItems(), PriceCalculateRespDTO.OrderItem::getCount, Integer::sum)); |
| 185 | 185 | tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源? |
| 186 | 186 | tradeOrderDO.setAdjustPrice(0).setPayed(false); // 支付信息 |
| 187 | 187 | tradeOrderDO.setDeliveryStatus(false); // 物流信息 |
| 188 | - tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息 | |
| 188 | + tradeOrderDO.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息 | |
| 189 | 189 | tradeOrderMapper.insert(tradeOrderDO); |
| 190 | 190 | return tradeOrderDO; |
| 191 | 191 | } |
| ... | ... | @@ -252,11 +252,52 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 252 | 252 | } |
| 253 | 253 | |
| 254 | 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 | 263 | int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus); |
| 257 | 264 | if (updateCount <= 0) { |
| 258 | 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 | 21 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; |
| 22 | 22 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; |
| 23 | 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 | 25 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; |
| 26 | 26 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; |
| 27 | 27 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; |
| ... | ... | @@ -182,7 +182,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest { |
| 182 | 182 | assertEquals(tradeOrderDO.getReceiverAreaId(), 3306L); |
| 183 | 183 | assertEquals(tradeOrderDO.getReceiverPostCode(), 85757); |
| 184 | 184 | assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村"); |
| 185 | - assertEquals(tradeOrderDO.getRefundStatus(), TradeOrderRefundStatusEnum.NONE.getStatus()); | |
| 185 | + assertEquals(tradeOrderDO.getAfterSaleStatus(), TradeOrderAfterSaleStatusEnum.NONE.getStatus()); | |
| 186 | 186 | assertEquals(tradeOrderDO.getRefundPrice(), 0); |
| 187 | 187 | assertEquals(tradeOrderDO.getCouponPrice(), 30); |
| 188 | 188 | assertEquals(tradeOrderDO.getPointPrice(), 10); | ... | ... |