feat(health):新增用户取消/退款订单的功能

This commit is contained in:
JIAN 2024-09-16 22:35:23 +08:00
parent a48fae5d30
commit db85c197a8
17 changed files with 365 additions and 86 deletions

View File

@ -1,6 +1,5 @@
package com.jzo2o.health.controller.user;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.health.model.dto.response.OrdersDetailResDTO;
import com.jzo2o.health.model.dto.response.OrdersResDTO;
import com.jzo2o.health.service.IOrderManagerService;
@ -40,6 +39,6 @@ public class OrdersQueryController {
@ApiImplicitParam(name = "id", value = "订单id", required = true, dataTypeClass = Long.class)
})
public OrdersDetailResDTO detail(@PathVariable("id") Long id) {
return BeanUtils.toBean(orderManagerService.getById(id), OrdersDetailResDTO.class);
return orderManagerService.getOrderById(id);
}
}

View File

@ -1,6 +1,8 @@
package com.jzo2o.health.controller.user;
import com.jzo2o.health.model.dto.request.OrdersCancelReqDTO;
import com.jzo2o.health.service.IOrderCancelService;
import com.jzo2o.health.service.IOrderRefundService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PutMapping;
@ -8,6 +10,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author itcast
*/
@ -15,14 +19,20 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user/orders")
@Api(tags = "用户端 - 取消订单退款相关接口")
public class OrdersRefundController {
@Resource
private IOrderCancelService orderCancelService;
@Resource
private IOrderRefundService orderRefundService;
@PutMapping("/cancel")
@ApiOperation("订单取消")
public void cancel(@RequestBody OrdersCancelReqDTO ordersCancelReqDTO) {
orderCancelService.cancelOrder(ordersCancelReqDTO);
}
@PutMapping("/refund")
@ApiOperation("订单退款")
public void refund(@RequestBody OrdersCancelReqDTO ordersCancelReqDTO) {
orderRefundService.refundOrder(ordersCancelReqDTO);
}
}
}

View File

@ -1,58 +0,0 @@
package com.jzo2o.health.handler;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.health.properties.OrdersJobProperties;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 订单相关定时任务
*
* @author itcast
* @create 2023/9/2 16:44
**/
@Slf4j
@Component
public class OrdersHandler {
@Resource
private RefundRecordApi refundRecordApi;
//解决同级方法调用事务失效问题
@Resource
private OrdersHandler orderHandler;
@Resource
private OrdersJobProperties ordersJobProperties;
/**
* 支付超时取消订单
* 每分钟执行一次
*/
@XxlJob(value = "cancelOverTimePayOrder")
public void cancelOverTimePayOrder() {
}
/**
* 订单退款异步任务
*/
@XxlJob(value = "handleRefundOrders")
public void handleRefundOrders() {
}
/**
* 订单退款处理
*
* @param id 订单id
* @param executionResultResDTO 第三方退款信息
*/
@Transactional(rollbackFor = Exception.class)
public void refundOrder(Long id, ExecutionResultResDTO executionResultResDTO) {
}
}

View File

@ -0,0 +1,40 @@
package com.jzo2o.health.job;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.health.model.domain.OrdersRefund;
import com.jzo2o.health.properties.OrdersJobProperties;
import com.jzo2o.health.service.IOrderRefundService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 订单退款任务
* @author JIAN
*/
@Slf4j
@Component
@SuppressWarnings("unused")
public class OrderRefundJob {
@Resource
private IOrderRefundService orderRefundService;
@Resource
private OrdersJobProperties ordersJobProperties;
/**
* 订单退款异步任务
*/
@XxlJob(value = "handleRefundOrders")
public void handleRefundOrders() {
List<OrdersRefund> latestRefundInfo = orderRefundService.getLatestRefundInfo(ordersJobProperties.getRefundOrderCount());
if (CollUtils.isEmpty(latestRefundInfo)) {
log.info("没有需要退款的订单");
}
latestRefundInfo.forEach(refundInfo -> orderRefundService.refundOrder(refundInfo));
}
}

View File

@ -1,23 +1,25 @@
package com.jzo2o.health.model.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 订单取消表
* </p>
*
* @author itcast
* @since 2023-11-07
*/
@Data
@Builder
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("orders_cancelled")
@ -28,7 +30,7 @@ public class OrdersCancelled implements Serializable {
/**
* 订单id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
@TableId(value = "id", type = IdType.NONE)
private Long id;
/**
@ -65,6 +67,4 @@ public class OrdersCancelled implements Serializable {
* 更新时间
*/
private LocalDateTime updateTime;
}
}

View File

@ -1,24 +1,26 @@
package com.jzo2o.health.model.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* <p>
* 订单退款表
* </p>
*
* @author itcast
* @since 2023-11-07
*/
@Data
@Builder
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("orders_refund")
@ -29,7 +31,7 @@ public class OrdersRefund implements Serializable {
/**
* 订单id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
@TableId(value = "id", type = IdType.NONE)
private Long id;
/**
@ -46,6 +48,4 @@ public class OrdersRefund implements Serializable {
* 创建时间
*/
private LocalDateTime createTime;
}
}

View File

@ -1,6 +1,8 @@
package com.jzo2o.health.model.dto.response;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.jzo2o.health.enums.OrderPayStatusEnum;
import com.jzo2o.health.enums.OrderStatusEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -30,13 +32,13 @@ public class OrdersDetailResDTO {
* 订单状态0未支付100待体检200已体检300已关闭400已取消
*/
@ApiModelProperty("订单状态0未支付100待体检200已体检300已关闭400已取消")
private Integer orderStatus;
private OrderStatusEnum orderStatus;
/**
* 支付状态0未支付1已支付2退款中3退款成功4退款失败
*/
@ApiModelProperty("支付状态0未支付1已支付2退款中3退款成功4退款失败")
private Integer payStatus;
private OrderPayStatusEnum payStatus;
/**
* 套餐id
@ -192,4 +194,4 @@ public class OrdersDetailResDTO {
*/
@ApiModelProperty("取消或退款原因")
private String cancelReason;
}
}

View File

@ -2,10 +2,20 @@ package com.jzo2o.health.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.health.model.domain.OrdersCancelled;
import com.jzo2o.health.model.dto.request.OrdersCancelReqDTO;
/**
* 取消订单相关业务层
* @author JIAN
*/
public interface IOrderCancelService extends IService<OrdersCancelled> {
/**
* 用户取消订单
*/
void cancelOrder(OrdersCancelReqDTO ordersCancelReqDTO);
/**
* 取消订单(内部使用)
*/
void cancelOrder(OrdersCancelled ordersCancelled);
}

View File

@ -12,7 +12,7 @@ import com.jzo2o.health.model.domain.Orders;
* @author itcast
* @since 2023-08-02
*/
public interface IOrdersCommonService extends IService<Orders> {
public interface IOrderCommonService extends IService<Orders> {
/**
* 更新指定id订单的状态
*/

View File

@ -5,6 +5,7 @@ import com.jzo2o.common.model.PageResult;
import com.jzo2o.health.model.domain.Orders;
import com.jzo2o.health.model.dto.request.OrdersPageQueryReqDTO;
import com.jzo2o.health.model.dto.response.AdminOrdersDetailResDTO;
import com.jzo2o.health.model.dto.response.OrdersDetailResDTO;
import com.jzo2o.health.model.dto.response.OrdersResDTO;
import java.util.List;
@ -28,4 +29,9 @@ public interface IOrderManagerService extends IService<Orders> {
* 管理端查询订单详情
*/
AdminOrdersDetailResDTO getAggregationInfo(Long id);
/**
* 用户端获取订单详情
*/
OrdersDetailResDTO getOrderById(Long id);
}

View File

@ -0,0 +1,29 @@
package com.jzo2o.health.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.health.model.domain.OrdersRefund;
import com.jzo2o.health.model.dto.request.OrdersCancelReqDTO;
import java.util.List;
/**
* 订单退款相关业务层
* @author JIAN
*/
public interface IOrderRefundService extends IService<OrdersRefund> {
/**
* 用户订单退款
*/
void refundOrder(OrdersCancelReqDTO ordersCancelReqDTO);
/**
* 调用API退款(内部使用)
*/
void refundOrder(OrdersRefund ordersRefund);
/**
* 获取最新的订单退款信息
* @param count 处理数量
*/
List<OrdersRefund> getLatestRefundInfo(Integer count);
}

View File

@ -1,10 +1,26 @@
package com.jzo2o.health.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.CurrentUserInfo;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.health.enums.OrderPayStatusEnum;
import com.jzo2o.health.enums.OrderStatusEnum;
import com.jzo2o.health.mapper.OrdersCancelledMapper;
import com.jzo2o.health.model.OrderUpdateStatusDTO;
import com.jzo2o.health.model.UserThreadLocal;
import com.jzo2o.health.model.domain.Orders;
import com.jzo2o.health.model.domain.OrdersCancelled;
import com.jzo2o.health.model.dto.request.OrdersCancelReqDTO;
import com.jzo2o.health.service.IOrderCancelService;
import com.jzo2o.health.service.IOrderCommonService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 取消订单相关业务层实现
@ -12,4 +28,52 @@ import org.springframework.stereotype.Service;
*/
@Service
public class OrderCancelServiceImpl extends ServiceImpl<OrdersCancelledMapper, OrdersCancelled> implements IOrderCancelService {
@Resource
private IOrderCommonService orderCommonService;
@Override
@Transactional
public void cancelOrder(OrdersCancelReqDTO ordersCancelReqDTO) {
CurrentUserInfo currentUser = UserThreadLocal.currentUser();
if (ObjectUtils.isEmpty(currentUser) || ObjectUtils.isEmpty(currentUser.getId())) {
throw new ForbiddenOperationException("无法获取用户信息无法取消");
}
Long orderId = ordersCancelReqDTO.getId();
Orders orders = orderCommonService.getById(orderId);
if (ObjectUtils.isEmpty(orders)) {
throw new ForbiddenOperationException("订单不存在无法取消");
}
if (orders.getOrderStatus() != OrderStatusEnum.NO_PAY || orders.getPayStatus() != OrderPayStatusEnum.NO_PAY) {
throw new ForbiddenOperationException("订单状态错误无法取消");
}
this.cancelOrder(OrdersCancelled.builder()
.id(orderId)
.cancelTime(LocalDateTime.now())
.cancelReason(ordersCancelReqDTO.getCancelReason())
.cancellerName(currentUser.getName())
.cancellerId(currentUser.getId())
.cancellerType(currentUser.getUserType())
.build());
}
@Override
@Transactional
public void cancelOrder(OrdersCancelled ordersCancelled) {
Long orderId = ordersCancelled.getId();
if (!orderCommonService.updateStatus(OrderUpdateStatusDTO.builder()
.id(orderId)
.originStatus(OrderStatusEnum.NO_PAY)
.targetStatus(OrderStatusEnum.CANCELLED)
.build())) {
throw new DBException("更新订单表失败");
}
if (!SqlHelper.retBool(baseMapper.insert(ordersCancelled))) {
throw new DBException("更新取消表失败");
}
}
}

View File

@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.health.mapper.OrdersMapper;
import com.jzo2o.health.model.OrderUpdateStatusDTO;
import com.jzo2o.health.model.domain.Orders;
import com.jzo2o.health.service.IOrdersCommonService;
import com.jzo2o.health.service.IOrderCommonService;
import org.springframework.stereotype.Service;
/**
@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
* @since 2023-08-02
*/
@Service
public class OrdersCommonServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCommonService {
public class OrderCommonServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrderCommonService {
@Override
public Boolean updateStatus(OrderUpdateStatusDTO orderUpdateStatusReqDTO) {
return lambdaUpdate()

View File

@ -31,7 +31,7 @@ import com.jzo2o.health.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.health.properties.TradeProperties;
import com.jzo2o.health.service.IMemberService;
import com.jzo2o.health.service.IOrderCreateService;
import com.jzo2o.health.service.IOrdersCommonService;
import com.jzo2o.health.service.IOrderCommonService;
import com.jzo2o.health.service.ISetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
@ -53,7 +53,7 @@ public class OrderCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> im
@Resource
private ISetmealService setmealService;
@Resource
private IOrdersCommonService ordersCommonService;
private IOrderCommonService ordersCommonService;
@Resource
private TradingApi tradingApi;
@Resource

View File

@ -2,6 +2,7 @@ package com.jzo2o.health.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.constants.UserType;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.BeanUtils;
@ -15,6 +16,7 @@ import com.jzo2o.health.model.domain.Orders;
import com.jzo2o.health.model.domain.OrdersCancelled;
import com.jzo2o.health.model.dto.request.OrdersPageQueryReqDTO;
import com.jzo2o.health.model.dto.response.AdminOrdersDetailResDTO;
import com.jzo2o.health.model.dto.response.OrdersDetailResDTO;
import com.jzo2o.health.model.dto.response.OrdersResDTO;
import com.jzo2o.health.service.IOrderCancelService;
import com.jzo2o.health.service.IOrderManagerService;
@ -22,6 +24,7 @@ import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@ -126,4 +129,37 @@ public class OrderManagerServiceImpl extends ServiceImpl<OrdersMapper, Orders> i
return adminOrdersDetailResDTO;
}
@Override
public OrdersDetailResDTO getOrderById(Long id) {
Orders orders = baseMapper.selectById(id);
if (ObjectUtils.isEmpty(orders)) {
return new OrdersDetailResDTO();
}
OrdersDetailResDTO ordersDetailResDTO = BeanUtils.toBean(orders, OrdersDetailResDTO.class);
// 订单超时则取消
if (orders.getOrderStatus() == OrderStatusEnum.NO_PAY
&& orders.getPayStatus() == OrderPayStatusEnum.NO_PAY
&& orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15))) {
orderCancelService.cancelOrder(OrdersCancelled.builder()
.id(id)
.cancelTime(LocalDateTime.now())
.cancelReason("订单超时自动取消")
.cancellerType(UserType.SYSTEM)
.build());
ordersDetailResDTO.setOrderStatus(OrderStatusEnum.CANCELLED);
}
if (orders.getOrderStatus() == OrderStatusEnum.CANCELLED
|| orders.getOrderStatus() == OrderStatusEnum.CLOSED) {
OrdersCancelled cancelInfo = orderCancelService.getById(id);
ordersDetailResDTO.setCancelReason(cancelInfo.getCancelReason());
ordersDetailResDTO.setCancelTime(cancelInfo.getCancelTime());
}
return ordersDetailResDTO;
}
}

View File

@ -0,0 +1,141 @@
package com.jzo2o.health.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.api.trade.enums.RefundStatusEnum;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.expcetions.ServerErrorException;
import com.jzo2o.common.model.CurrentUserInfo;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.health.enums.OrderPayStatusEnum;
import com.jzo2o.health.enums.OrderStatusEnum;
import com.jzo2o.health.mapper.OrdersRefundMapper;
import com.jzo2o.health.model.OrderUpdateStatusDTO;
import com.jzo2o.health.model.UserThreadLocal;
import com.jzo2o.health.model.domain.Orders;
import com.jzo2o.health.model.domain.OrdersCancelled;
import com.jzo2o.health.model.domain.OrdersRefund;
import com.jzo2o.health.model.dto.request.OrdersCancelReqDTO;
import com.jzo2o.health.service.IOrderCancelService;
import com.jzo2o.health.service.IOrderCommonService;
import com.jzo2o.health.service.IOrderRefundService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 订单退款相关业务层实现
* @author JIAN
*/
@Service
public class OrderRefundServiceImpl extends ServiceImpl<OrdersRefundMapper, OrdersRefund> implements IOrderRefundService {
@Resource
private IOrderCommonService orderCommonService;
@Resource
private IOrderCancelService orderCancelService;
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private RefundRecordApi refundRecordApi;
@Override
@Transactional
public void refundOrder(OrdersCancelReqDTO ordersCancelReqDTO) {
CurrentUserInfo currentUser = UserThreadLocal.currentUser();
if (ObjectUtils.isEmpty(currentUser) || ObjectUtils.isEmpty(currentUser.getId())) {
throw new ForbiddenOperationException("无法获取用户信息无法取消");
}
Long orderId = ordersCancelReqDTO.getId();
Orders orders = orderCommonService.getById(orderId);
if (ObjectUtils.isEmpty(orders)) {
throw new ForbiddenOperationException("订单不存在无法取消");
}
if (orders.getOrderStatus() != OrderStatusEnum.WAITING_CHECKUP || orders.getPayStatus() != OrderPayStatusEnum.PAY_SUCCESS) {
throw new ForbiddenOperationException("订单状态错误无法取消");
}
if (!orderCommonService.updateStatus(OrderUpdateStatusDTO.builder()
.id(orderId)
.originStatus(OrderStatusEnum.WAITING_CHECKUP)
.targetStatus(OrderStatusEnum.CLOSED)
.payStatus(OrderPayStatusEnum.REFUNDING)
.build())) {
throw new DBException("更新订单表失败");
}
if (!orderCancelService.save(OrdersCancelled.builder()
.id(orderId)
.cancelTime(LocalDateTime.now())
.cancelReason(ordersCancelReqDTO.getCancelReason())
.cancellerName(currentUser.getName())
.cancellerId(currentUser.getId())
.cancellerType(currentUser.getUserType())
.build())) {
throw new DBException("更新取消表失败");
}
OrdersRefund ordersRefund = OrdersRefund.builder()
.id(orderId)
.tradingOrderNo(orders.getTradingOrderNo())
.realPayAmount(/*orders.getSetmealPrice()*/ new BigDecimal("0.01"))
.build();
if (!SqlHelper.retBool(baseMapper.insert(ordersRefund))) {
throw new DBException("更新退款表失败");
}
new Thread(() -> this.refundOrder(ordersRefund)).start();
}
@Override
public void refundOrder(OrdersRefund ordersRefund) {
ExecutionResultResDTO executionResultResDTO = refundRecordApi.refundTrading(ordersRefund.getTradingOrderNo(), ordersRefund.getRealPayAmount());
if (ObjectUtils.isEmpty(executionResultResDTO)) {
throw new ServerErrorException("退款接口调用失败请重试");
}
Integer refundStatus = executionResultResDTO.getRefundStatus();
OrderPayStatusEnum payStatus;
if (RefundStatusEnum.SUCCESS.getCode().equals(refundStatus)) {
payStatus = OrderPayStatusEnum.REFUND_SUCCESS;
} else if (RefundStatusEnum.FAIL.getCode().equals(refundStatus)) {
payStatus = OrderPayStatusEnum.REFUND_FAIL;
} else {
return;
}
transactionTemplate.executeWithoutResult(status -> {
if (!orderCommonService.updateStatus(OrderUpdateStatusDTO.builder()
.id(ordersRefund.getId())
.targetStatus(OrderStatusEnum.CLOSED)
.refundNo(executionResultResDTO.getRefundNo())
.refundId(executionResultResDTO.getRefundId())
.payStatus(payStatus)
.build())) {
throw new DBException("更新订单表失败");
}
if (!SqlHelper.retBool(baseMapper.deleteById(ordersRefund.getId()))) {
throw new DBException("更新退款表失败");
}
});
}
@Override
@SuppressWarnings("unchecked")
public List<OrdersRefund> getLatestRefundInfo(Integer count) {
return lambdaQuery()
.orderByDesc(OrdersRefund::getCreateTime)
.last("LIMIT " + count)
.list();
}
}