更新了优惠券模块

This commit is contained in:
chen-xin-zhi 2025-02-07 20:55:49 +08:00
parent 9082882cab
commit f1dfd35960
24 changed files with 542 additions and 85 deletions

View File

@ -1,8 +1,6 @@
package com.cultural.heritage.controller.good; package com.cultural.heritage.controller.good;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@ -11,7 +9,6 @@ import com.cultural.heritage.annotation.AuthCheck;
import com.cultural.heritage.common.BaseResponse; import com.cultural.heritage.common.BaseResponse;
import com.cultural.heritage.common.ErrorCode; import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.common.ResultUtils; import com.cultural.heritage.common.ResultUtils;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.constant.UserConstant; import com.cultural.heritage.constant.UserConstant;
import com.cultural.heritage.exception.BusinessException; import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.exception.ThrowUtils; import com.cultural.heritage.exception.ThrowUtils;
@ -30,14 +27,12 @@ import com.cultural.heritage.model.vo.coupon.UserCouponVO;
import com.cultural.heritage.service.good.CouponService; import com.cultural.heritage.service.good.CouponService;
import com.cultural.heritage.service.good.UserCouponService; import com.cultural.heritage.service.good.UserCouponService;
import com.cultural.heritage.service.user.UserService; import com.cultural.heritage.service.user.UserService;
import com.cultural.heritage.utils.MultiDelayMessage;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -65,8 +60,6 @@ public class CouponController {
private UserCouponService userCouponService; private UserCouponService userCouponService;
@Resource
private RabbitTemplate rabbitTemplate;
/** /**
@ -100,18 +93,8 @@ public class CouponController {
boolean result = couponService.save(coupon); boolean result = couponService.save(coupon);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "优惠券添加失败"); ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "优惠券添加失败");
// 延迟检查优惠券是否过期 // 向消息队列中发送优惠券创建的消息
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(coupon.getId()); couponService.sendCouponCreateMessage(coupon);
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_COUPON_ROUTING_KEY, msg, message -> {
//计算优惠券截止日期和当前的时间差
DateTime now = new DateTime();
DateTime end = DateUtil.parse(coupon.getEndTime());
long seconds = DateUtil.between(now, end, DateUnit.SECOND);
// 添加延迟消息属性
message.getMessageProperties().setDelay((int) (seconds * 1000));
return message;
});
return ResultUtils.success(true); return ResultUtils.success(true);
} }
@ -251,18 +234,8 @@ public class CouponController {
boolean save = userCouponService.save(userCoupon); boolean save = userCouponService.save(userCoupon);
ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "兑换记录插入失败"); ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "兑换记录插入失败");
// 延迟检查优惠券是否过期 // 向消息队列中发送用户优惠券创建的消息
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(userCoupon.getId()); couponService.sendUserCouponCreateMessage(userCoupon, coupon);
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_USER_COUPON_ROUTING_KEY, msg, message -> {
//计算优惠券截止日期和当前的时间差
DateTime now = new DateTime();
DateTime end = DateUtil.parse(coupon.getEndTime());
long seconds = DateUtil.between(now, end, DateUnit.SECOND);
// 添加延迟消息属性
message.getMessageProperties().setDelay((int) (seconds * 1000));
return message;
});
// 更新用户积分 // 更新用户积分
loginUser.setPoints(loginUser.getPoints() - requirePoints); loginUser.setPoints(loginUser.getPoints() - requirePoints);
@ -388,8 +361,8 @@ public class CouponController {
if (usableCouponQueryRequest == null) { if (usableCouponQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR); throw new BusinessException(ErrorCode.PARAMS_ERROR);
} }
BigDecimal conditionAmount = usableCouponQueryRequest.getConditionAmount(); BigDecimal currentAmount = usableCouponQueryRequest.getCurrentAmount();
if (conditionAmount.compareTo(BigDecimal.ZERO) <= 0) { if (currentAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "订单金额参数错误"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "订单金额参数错误");
} }
User loginUser = userService.getLoginUser(request); User loginUser = userService.getLoginUser(request);
@ -403,9 +376,9 @@ public class CouponController {
// 获取()可使用的优惠券 // 获取()可使用的优惠券
List<UserCoupon> userCoupons = userCouponList.stream().filter(userCoupon -> { List<UserCoupon> userCoupons = userCouponList.stream().filter(userCoupon -> {
CouponVO couponVO = userCoupon.getCouponVO(); CouponVO couponVO = userCoupon.getCouponVO();
BigDecimal couponAmount = couponVO.getConditionAmount(); BigDecimal standardAmount = couponVO.getStandardAmount();
Boolean isAvailable = usableCouponQueryRequest.getIsAvailable(); Boolean isAvailable = usableCouponQueryRequest.getIsAvailable();
int result = conditionAmount.compareTo(couponAmount); int result = currentAmount.compareTo(standardAmount);
return isAvailable == (result >= 0); return isAvailable == (result >= 0);
}).toList(); }).toList();

View File

@ -7,7 +7,6 @@ import com.cultural.heritage.annotation.AuthCheck;
import com.cultural.heritage.common.BaseResponse; import com.cultural.heritage.common.BaseResponse;
import com.cultural.heritage.common.ErrorCode; import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.common.ResultUtils; import com.cultural.heritage.common.ResultUtils;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.constant.OrderStatusConstant; import com.cultural.heritage.constant.OrderStatusConstant;
import com.cultural.heritage.constant.UserConstant; import com.cultural.heritage.constant.UserConstant;
import com.cultural.heritage.exception.BusinessException; import com.cultural.heritage.exception.BusinessException;
@ -17,23 +16,23 @@ import com.cultural.heritage.model.dto.cart.CartOrderAddRequest;
import com.cultural.heritage.model.dto.cart.CartOrderItemAddRequest; import com.cultural.heritage.model.dto.cart.CartOrderItemAddRequest;
import com.cultural.heritage.model.dto.order.OrderQueryRequest; import com.cultural.heritage.model.dto.order.OrderQueryRequest;
import com.cultural.heritage.model.dto.order.OrderUpdateRequest; import com.cultural.heritage.model.dto.order.OrderUpdateRequest;
import com.cultural.heritage.model.dto.order.capital.GeneralGoodSingleBuyAddRequest;
import com.cultural.heritage.model.dto.order.capital.OrderItemMainInfoAddRequest; import com.cultural.heritage.model.dto.order.capital.OrderItemMainInfoAddRequest;
import com.cultural.heritage.model.dto.order.capital.OrderMainInfoAddRequest; import com.cultural.heritage.model.dto.order.capital.OrderMainInfoAddRequest;
import com.cultural.heritage.model.dto.snapshot.GoodSnapshot; import com.cultural.heritage.model.dto.snapshot.GoodSnapshot;
import com.cultural.heritage.model.entity.*; import com.cultural.heritage.model.entity.*;
import com.cultural.heritage.model.vo.order.OrderVO; import com.cultural.heritage.model.vo.order.OrderVO;
import com.cultural.heritage.service.good.CartRecordService; import com.cultural.heritage.service.good.CartRecordService;
import com.cultural.heritage.service.good.CouponService;
import com.cultural.heritage.service.good.GoodService; import com.cultural.heritage.service.good.GoodService;
import com.cultural.heritage.service.order.OrderItemService; import com.cultural.heritage.service.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService; import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.service.user.UserService; import com.cultural.heritage.service.user.UserService;
import com.cultural.heritage.utils.MultiDelayMessage;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -78,9 +77,10 @@ public class OrderController {
private GoodService goodService; private GoodService goodService;
@Resource @Resource
private RabbitTemplate rabbitTemplate; private CouponService couponService;
@ -98,14 +98,14 @@ public class OrderController {
User loginUser = userService.getLoginUser(request); User loginUser = userService.getLoginUser(request);
Long userId = loginUser.getId(); Long userId = loginUser.getId();
// 封装成订单创建请求体
OrderMainInfoAddRequest orderMainInfoAddRequest = new OrderMainInfoAddRequest();
BeanUtils.copyProperties(cartOrderAddRequest, orderMainInfoAddRequest);
// 校验购物车数据是否准确 // 校验购物车数据是否准确
List<CartOrderItemAddRequest> cartOrderItemAddRequestList = cartOrderAddRequest.getCartOrderItemAddRequestList(); List<CartOrderItemAddRequest> cartOrderItemAddRequestList = cartOrderAddRequest.getCartOrderItemAddRequestList();
boolean isAccurate = cartRecordService.validIsConsistent(cartOrderItemAddRequestList); boolean isAccurate = cartRecordService.validIsConsistent(cartOrderItemAddRequestList);
ThrowUtils.throwIf(!isAccurate, ErrorCode.SYSTEM_ERROR, "当前购物车中的某些商品已下架或者库存不足,无法生成订单"); ThrowUtils.throwIf(!isAccurate, ErrorCode.OPERATION_ERROR, "当前购物车中的某些商品已下架或者库存不足,无法生成订单");
// 封装成订单主要信息请求体
OrderMainInfoAddRequest orderMainInfoAddRequest = new OrderMainInfoAddRequest();
BeanUtils.copyProperties(cartOrderAddRequest, orderMainInfoAddRequest);
// 获取购物车id列表 // 获取购物车id列表
List<Long> cartIds = cartOrderItemAddRequestList.stream().map(CartOrderItemAddRequest::getCartRecordId).toList(); List<Long> cartIds = cartOrderItemAddRequestList.stream().map(CartOrderItemAddRequest::getCartRecordId).toList();
@ -140,6 +140,11 @@ public class OrderController {
Integer quantity = map.get(cartRecordId); Integer quantity = map.get(cartRecordId);
totalAmount = totalAmount.add(good.getPrice().multiply(BigDecimal.valueOf(quantity))); totalAmount = totalAmount.add(good.getPrice().multiply(BigDecimal.valueOf(quantity)));
} }
Long couponId = orderMainInfoAddRequest.getCouponId();
Coupon coupon = couponService.getById(couponId);
ThrowUtils.throwIf(coupon == null, ErrorCode.OPERATION_ERROR, "优惠券不存在");
BigDecimal conditionAmount = coupon.getConditionAmount();
totalAmount = totalAmount.subtract(conditionAmount);
orderMainInfoAddRequest.setTotalAmount(totalAmount); orderMainInfoAddRequest.setTotalAmount(totalAmount);
// 封装成订单明细主要信息请求体列表 // 封装成订单明细主要信息请求体列表
@ -157,17 +162,8 @@ public class OrderController {
// 创建通用订单(常规类和服务类商品) // 创建通用订单(常规类和服务类商品)
Long orderId = orderService.createCommonOrder(orderMainInfoAddRequest, userId, true, cartIds); Long orderId = orderService.createCommonOrder(orderMainInfoAddRequest, userId, true, cartIds);
// 延迟检查订单状态信息 // 向消息队列中发送订单创建的消息
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(orderId, orderService.sendCreateOrderMessage(orderId);
10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L, 60000L, 60000L, 120000L, 300000L, 600000L, 600000L);
// 10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L, 60000L, 60000L, 120000L, 300000L, 600000L, 600000L
int delayValue = msg.removeNextDelay().intValue();
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_ORDER_ROUTING_KEY, msg, message -> {
// 添加延迟消息属性
message.getMessageProperties().setDelay(delayValue);
return message;
});
return ResultUtils.success(orderId); return ResultUtils.success(orderId);
} }
@ -176,38 +172,36 @@ public class OrderController {
/** /**
* 小程序端用户创建常规类商品订单 * 小程序端用户创建常规类商品订单
* @param orderMainInfoAddRequest 订单创建请求体 * @param generalGoodSingleBuyAddRequest 常规类类商品单独购买创建请求体
* @return 是否创建成功 * @return 是否创建成功
*/ */
@PostMapping("/add") @PostMapping("/add")
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Operation(summary = "小程序端用户创建常规类商品订单", description = "参数订单创建请求体权限所有人方法名addOrder") @Operation(summary = "小程序端用户创建常规类商品订单", description = "参数订单创建请求体权限所有人方法名addOrder")
public BaseResponse<Long> addOrder(@RequestBody OrderMainInfoAddRequest orderMainInfoAddRequest, HttpServletRequest request) { public BaseResponse<Long> addOrder(@RequestBody GeneralGoodSingleBuyAddRequest generalGoodSingleBuyAddRequest, HttpServletRequest request) {
if (orderMainInfoAddRequest == null) { if (generalGoodSingleBuyAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR); throw new BusinessException(ErrorCode.PARAMS_ERROR);
} }
// 校验用户当前是否处于登录态 // 校验用户当前是否处于登录态
User loginUser = userService.getLoginUser(request); User loginUser = userService.getLoginUser(request);
Long userId = loginUser.getId(); Long userId = loginUser.getId();
// 校验单独购买的商品数据是否准确 // 校验单独购买的商品数据是否准确
orderService.validSingleGoodOrder(orderMainInfoAddRequest); orderService.validSingleGoodOrder(generalGoodSingleBuyAddRequest);
// 封装成订单主要信息请求体
OrderMainInfoAddRequest orderMainInfoAddRequest = new OrderMainInfoAddRequest();
BeanUtils.copyProperties(generalGoodSingleBuyAddRequest, orderMainInfoAddRequest);
// 计算单个商品购买的总金额 // 计算单个商品购买的总金额
BigDecimal totalAmount = orderService.calculateTotalAmount(orderMainInfoAddRequest); BigDecimal totalAmount = orderService.calculateTotalAmount(orderMainInfoAddRequest);
orderMainInfoAddRequest.setTotalAmount(totalAmount); orderMainInfoAddRequest.setTotalAmount(totalAmount);
// 创建通用订单(常规类和服务类商品) // 创建通用订单(常规类和服务类商品)
Long orderId = orderService.createCommonOrder(orderMainInfoAddRequest, userId, false, null); Long orderId = orderService.createCommonOrder(orderMainInfoAddRequest, userId, false, null);
// 延迟检查订单状态信息 // 向消息队列中发送订单创建的消息
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(orderId, orderService.sendCreateOrderMessage(orderId);
10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L, 60000L, 60000L, 120000L, 300000L, 600000L, 600000L);
// 10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L, 60000L, 60000L, 120000L, 300000L, 600000L, 600000L
int delayValue = msg.removeNextDelay().intValue();
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_ORDER_ROUTING_KEY, msg, message -> {
// 添加延迟消息属性
message.getMessageProperties().setDelay(delayValue);
return message;
});
return ResultUtils.success(orderId); return ResultUtils.success(orderId);
} }
@ -282,11 +276,31 @@ public class OrderController {
queryWrapper.eq("userId", id); queryWrapper.eq("userId", id);
queryWrapper.orderByDesc("id"); queryWrapper.orderByDesc("id");
List<Order> orders = orderService.list(queryWrapper); List<Order> orders = orderService.list(queryWrapper);
QueryWrapper<OrderItems> myOrderItemsQueryWrapper = new QueryWrapper<>();
List<Long> orderIds = orders.stream().map(Order::getId).toList();
myOrderItemsQueryWrapper.in("orderId", orderIds);
List<OrderItems> myOrderItems = orderItemService.list(myOrderItemsQueryWrapper);
// 封装map集合订单id, 订单明细列表
Map<Long, List<OrderItems>> map = new HashMap<>();
for (OrderItems orderItems : myOrderItems) {
Long orderId = orderItems.getOrderId();
List<OrderItems> orderItemsArrayList = map.get(orderId);
if (orderItemsArrayList == null) {
orderItemsArrayList = new ArrayList<>();
}
orderItemsArrayList.add(orderItems);
map.put(orderId, orderItemsArrayList);
}
List<OrderVO> orderVOS = orders.stream().map(order -> { List<OrderVO> orderVOS = orders.stream().map(order -> {
Long orderId = order.getId(); Long orderId = order.getId();
QueryWrapper<OrderItems> orderItemsQueryWrapper = new QueryWrapper<>(); QueryWrapper<OrderItems> orderItemsQueryWrapper = new QueryWrapper<>();
orderItemsQueryWrapper.eq("orderId", orderId); orderItemsQueryWrapper.eq("orderId", orderId);
List<OrderItems> orderItemsList = orderItemService.list(orderItemsQueryWrapper); List<OrderItems> orderItemsList = map.get(orderId);
OrderVO orderVO = new OrderVO(); OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(order, orderVO); BeanUtils.copyProperties(order, orderVO);
orderVO.setOrderItemList(orderItemsList); orderVO.setOrderItemList(orderItemsList);

View File

@ -11,7 +11,9 @@ import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.exception.ThrowUtils; import com.cultural.heritage.exception.ThrowUtils;
import com.cultural.heritage.model.dto.CommonRequest; import com.cultural.heritage.model.dto.CommonRequest;
import com.cultural.heritage.model.entity.Order; import com.cultural.heritage.model.entity.Order;
import com.cultural.heritage.model.entity.OrderItems;
import com.cultural.heritage.model.entity.User; import com.cultural.heritage.model.entity.User;
import com.cultural.heritage.service.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService; import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.service.user.UserService; import com.cultural.heritage.service.user.UserService;
import com.cultural.heritage.service.wx.WeChatService; import com.cultural.heritage.service.wx.WeChatService;
@ -46,6 +48,10 @@ public class WeChatPayController {
@Resource @Resource
private WeChatService weChatService; private WeChatService weChatService;
@Resource
private OrderItemService orderItemService;
/** /**
* JSAPI 下单 * JSAPI 下单
@ -83,10 +89,14 @@ public class WeChatPayController {
/** /**
* 退款仅管理员 * 退款仅管理员
* @param commonRequest 订单id
*/ */
@PostMapping("/refund/create") @PostMapping("/refund/create")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE) @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Refund> createRefund(@RequestBody CommonRequest commonRequest) { public BaseResponse<Refund> createRefund(@RequestBody CommonRequest commonRequest) {
if (commonRequest == null || commonRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Long orderId = commonRequest.getId(); Long orderId = commonRequest.getId();
Order order = ordersService.getById(orderId); Order order = ordersService.getById(orderId);
ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在");
@ -94,6 +104,26 @@ public class WeChatPayController {
return ResultUtils.success(refund); return ResultUtils.success(refund);
} }
/**
* Web管理员退款订单明细
* @param commonRequest 订单明细id
*/
@PostMapping("/refund/part/create")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Refund> createPartRefund(@RequestBody CommonRequest commonRequest) {
if (commonRequest == null || commonRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Long orderItemsId = commonRequest.getId();
OrderItems orderItems = orderItemService.getById(orderItemsId);
ThrowUtils.throwIf(orderItems == null, ErrorCode.OPERATION_ERROR, "订单明细不存在");
Refund refund = weChatService.refundPartPayment(String.valueOf(orderItemsId), orderItems.getItemTotalAmount());
return ResultUtils.success(refund);
}
/** /**
* 退款回调 * 退款回调
*/ */

View File

@ -0,0 +1,7 @@
package com.cultural.heritage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cultural.heritage.model.entity.RefundRecord;
public interface RefundRecordMapper extends BaseMapper<RefundRecord> {
}

View File

@ -9,7 +9,7 @@ import java.math.BigDecimal;
@Data @Data
@Schema(description = "优惠券添加请求体", requiredProperties = @Schema(description = "优惠券添加请求体", requiredProperties =
{"name", "conditionAmount", "requirePoints", "content", "startTime", "endTime", "description"}) {"name", "standardAmount", "conditionAmount", "requirePoints", "content", "startTime", "endTime", "description"})
public class CouponAddRequest implements Serializable { public class CouponAddRequest implements Serializable {
@ -19,6 +19,14 @@ public class CouponAddRequest implements Serializable {
@Schema(description = "优惠券名称", example = "满200减50") @Schema(description = "优惠券名称", example = "满200减50")
private String name; private String name;
/**
* 标准金额
*/
@Schema(description = "标准金额", example = "200")
private BigDecimal standardAmount;
/** /**
* 满减金额 * 满减金额
*/ */

View File

@ -24,6 +24,12 @@ public class CouponUpdateRequest implements Serializable {
@Schema(description = "优惠券名称", example = "满200减50") @Schema(description = "优惠券名称", example = "满200减50")
private String name; private String name;
/**
* 标准金额
*/
@Schema(description = "标准金额", example = "200")
private BigDecimal standardAmount;
/** /**
* 满减金额 * 满减金额
*/ */

View File

@ -9,7 +9,7 @@ import java.math.BigDecimal;
@Data @Data
@Schema(description = "可用优惠券请求体", requiredProperties = @Schema(description = "可用优惠券请求体", requiredProperties =
{"conditionAmount", "isAvailable"}) {"standardAmount", "isAvailable"})
public class UsableCouponQueryRequest implements Serializable { public class UsableCouponQueryRequest implements Serializable {
@ -17,7 +17,7 @@ public class UsableCouponQueryRequest implements Serializable {
* 订单金额 * 订单金额
*/ */
@Schema(description = "订单金额", example = "50") @Schema(description = "订单金额", example = "50")
private BigDecimal conditionAmount; private BigDecimal currentAmount;
/** /**

View File

@ -0,0 +1,68 @@
package com.cultural.heritage.model.dto.order.capital;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Data
@Schema(description = "常规类商品单独购买创建请求体", requiredProperties = {"userId", "userName", "orderNumber", "addressId", "contactsId",
"couponId", "orderStatus", "note", "orderItemMainInfoAddRequestList"})
public class GeneralGoodSingleBuyAddRequest implements Serializable {
/**
* 订单类别
*/
@Schema(description = "订单类别", example = "product")
private String orderType;
/**
* 用户昵称
*/
@Schema(description = "用户昵称", example = "Hello")
private String userName;
/**
* 地址id
*/
@Schema(description = "地址id(id > 0)", example = "12")
private Long addressId;
/**
* 联系人id
*/
@Schema(description = "联系人id(id > 0)", example = "3")
private Long contactsId;
/**
* 优惠券id
*/
@Schema(description = "优惠券id(id > 0)", example = "2")
private Long couponId;
/**
* 订单备注
*/
@Schema(description = "订单备注", example = "希望能完整体验非遗文化,保持产品原貌,妥善包装")
private String note;
/**
* 订单主要明细信息
*/
@Schema(description = "订单主要明细信息")
private List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,39 @@
package com.cultural.heritage.model.dto.refund;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@Schema(description = "退款记录添加请求体", requiredProperties = {"outTradeNo", "outRefundNo", "refundAmount"})
public class RefundRecordAddRequest implements Serializable {
/**
* 商户单号
*/
@Schema(description = "商户单号", example = "20250206220922442039734342343")
private String outTradeNo;
/**
* 退款单号
*/
@Schema(description = "退款单号", example = "202502062209224420397")
private String outRefundNo;
/**
* 退款金额
*/
@Schema(description = "退款金额", example = "100.00")
private BigDecimal refundAmount;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -46,6 +46,12 @@ public class Coupon implements Serializable {
private BigDecimal conditionAmount; private BigDecimal conditionAmount;
/**
* 标准金额
*/
private BigDecimal standardAmount;
/** /**
* 需要的积分 * 需要的积分
*/ */

View File

@ -0,0 +1,68 @@
package com.cultural.heritage.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 退款记录表
* @TableName refund_record
*/
@Data
@TableName(value = "refund_record")
@Schema(description = "退款记录", requiredProperties = {"id", "outTradeNo", "outRefundNo", "refundAmount"})
public class RefundRecord implements Serializable {
/**
* 退款记录id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 商户单号
*/
private String outTradeNo;
/**
* 退款单号
*/
private String outRefundNo;
/**
* 退款金额
*/
private BigDecimal refundAmount;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否删除
*/
private Integer isDelete;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -28,6 +28,12 @@ public class CouponVO implements Serializable {
private String content; private String content;
/**
* 标准金额
*/
private BigDecimal standardAmount;
/** /**
* 满减金额 * 满减金额
*/ */

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.cultural.heritage.model.dto.coupon.CouponQueryRequest; import com.cultural.heritage.model.dto.coupon.CouponQueryRequest;
import com.cultural.heritage.model.entity.Coupon; import com.cultural.heritage.model.entity.Coupon;
import com.cultural.heritage.model.entity.UserCoupon;
public interface CouponService extends IService<Coupon> { public interface CouponService extends IService<Coupon> {
@ -24,4 +25,17 @@ public interface CouponService extends IService<Coupon> {
* 校验 * 校验
*/ */
void validCoupon(Coupon coupon, boolean update); void validCoupon(Coupon coupon, boolean update);
/**
* 向消息队列中发送优惠券创建的消息
*/
void sendCouponCreateMessage(Coupon coupon);
/**
* 向消息队列中发送用户优惠券创建的消息
*/
void sendUserCouponCreateMessage(UserCoupon userCoupon, Coupon coupon);
} }

View File

@ -1,17 +1,25 @@
package com.cultural.heritage.service.good.impl; package com.cultural.heritage.service.good.impl;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cultural.heritage.common.ErrorCode; import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.constant.CommonConstant; import com.cultural.heritage.constant.CommonConstant;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.exception.BusinessException; import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.mapper.CouponMapper; import com.cultural.heritage.mapper.CouponMapper;
import com.cultural.heritage.model.dto.coupon.CouponQueryRequest; import com.cultural.heritage.model.dto.coupon.CouponQueryRequest;
import com.cultural.heritage.model.entity.Coupon; import com.cultural.heritage.model.entity.Coupon;
import com.cultural.heritage.model.entity.UserCoupon;
import com.cultural.heritage.service.good.CouponService; import com.cultural.heritage.service.good.CouponService;
import com.cultural.heritage.utils.MultiDelayMessage;
import com.cultural.heritage.utils.SqlUtils; import com.cultural.heritage.utils.SqlUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -21,6 +29,10 @@ import java.util.Date;
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService { public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {
@Resource
private RabbitTemplate rabbitTemplate;
@Override @Override
public QueryWrapper<Coupon> getQueryWrapper(CouponQueryRequest couponQueryRequest) { public QueryWrapper<Coupon> getQueryWrapper(CouponQueryRequest couponQueryRequest) {
if (couponQueryRequest == null) { if (couponQueryRequest == null) {
@ -57,6 +69,7 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
Long id = coupon.getId(); Long id = coupon.getId();
String name = coupon.getName(); String name = coupon.getName();
BigDecimal conditionAmount = coupon.getConditionAmount(); BigDecimal conditionAmount = coupon.getConditionAmount();
BigDecimal standardAmount = coupon.getStandardAmount();
Integer requirePoints = coupon.getRequirePoints(); Integer requirePoints = coupon.getRequirePoints();
String startTime = coupon.getStartTime(); String startTime = coupon.getStartTime();
String endTime = coupon.getEndTime(); String endTime = coupon.getEndTime();
@ -69,9 +82,16 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数id错误"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数id错误");
} }
} }
if (ObjectUtils.isEmpty(standardAmount) || standardAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "标准价格错误");
}
if (ObjectUtils.isEmpty(conditionAmount) || conditionAmount.compareTo(BigDecimal.ZERO) <= 0) { if (ObjectUtils.isEmpty(conditionAmount) || conditionAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "满减价格错误"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "满减价格错误");
} }
if (conditionAmount.compareTo(standardAmount) > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "满减价格不能超过标准价格");
}
if (ObjectUtils.isEmpty(requirePoints) || requirePoints <= 0) { if (ObjectUtils.isEmpty(requirePoints) || requirePoints <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "积分参数错误"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "积分参数错误");
} }
@ -82,4 +102,46 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
throw new BusinessException(ErrorCode.PARAMS_ERROR, "有效期开始时间晚于有效期截止时间"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "有效期开始时间晚于有效期截止时间");
} }
} }
/**
* 向消息队列中发送优惠券创建的消息
*/
@Override
public void sendCouponCreateMessage(Coupon coupon) {
// 延迟检查优惠券是否过期
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(coupon.getId());
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_COUPON_ROUTING_KEY, msg, message -> {
//计算优惠券截止日期和当前的时间差
DateTime now = new DateTime();
DateTime end = DateUtil.parse(coupon.getEndTime());
long seconds = DateUtil.between(now, end, DateUnit.SECOND);
// 添加延迟消息属性
message.getMessageProperties().setDelay((int) (seconds * 1000));
return message;
});
}
/**
* 向消息队列中发送用户优惠券创建的消息
*/
@Override
public void sendUserCouponCreateMessage(UserCoupon userCoupon, Coupon coupon) {
// 延迟检查优惠券是否过期
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(userCoupon.getId());
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_USER_COUPON_ROUTING_KEY, msg, message -> {
//计算优惠券截止日期和当前的时间差
DateTime now = new DateTime();
DateTime end = DateUtil.parse(coupon.getEndTime());
long seconds = DateUtil.between(now, end, DateUnit.SECOND);
// 添加延迟消息属性
message.getMessageProperties().setDelay((int) (seconds * 1000));
return message;
});
}
} }

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.cultural.heritage.model.dto.order.OrderAddRequest; import com.cultural.heritage.model.dto.order.OrderAddRequest;
import com.cultural.heritage.model.dto.order.OrderQueryRequest; import com.cultural.heritage.model.dto.order.OrderQueryRequest;
import com.cultural.heritage.model.dto.order.capital.GeneralGoodSingleBuyAddRequest;
import com.cultural.heritage.model.dto.order.capital.OrderMainInfoAddRequest; import com.cultural.heritage.model.dto.order.capital.OrderMainInfoAddRequest;
import com.cultural.heritage.model.entity.Order; import com.cultural.heritage.model.entity.Order;
@ -41,7 +42,7 @@ public interface OrderService extends IService<Order> {
/** /**
* 校验单独购买的商品数据是否准确 * 校验单独购买的商品数据是否准确
*/ */
void validSingleGoodOrder(OrderMainInfoAddRequest orderMainInfoAddRequest); void validSingleGoodOrder(GeneralGoodSingleBuyAddRequest generalGoodSingleBuyAddRequest);
@ -49,4 +50,11 @@ public interface OrderService extends IService<Order> {
* 计算单个商品购买的总金额 * 计算单个商品购买的总金额
*/ */
BigDecimal calculateTotalAmount(OrderMainInfoAddRequest orderMainInfoAddRequest); BigDecimal calculateTotalAmount(OrderMainInfoAddRequest orderMainInfoAddRequest);
/**
* 向消息队列中发送订单创建的消息
*/
void sendCreateOrderMessage(Long orderId);
} }

View File

@ -0,0 +1,7 @@
package com.cultural.heritage.service.order;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cultural.heritage.model.entity.RefundRecord;
public interface RefundRecordService extends IService<RefundRecord> {
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cultural.heritage.common.ErrorCode; import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.constant.CommonConstant; import com.cultural.heritage.constant.CommonConstant;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.constant.OrderStatusConstant; import com.cultural.heritage.constant.OrderStatusConstant;
import com.cultural.heritage.exception.BusinessException; import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.exception.ThrowUtils; import com.cultural.heritage.exception.ThrowUtils;
@ -12,25 +13,30 @@ import com.cultural.heritage.mapper.*;
import com.cultural.heritage.model.dto.order.OrderAddRequest; import com.cultural.heritage.model.dto.order.OrderAddRequest;
import com.cultural.heritage.model.dto.order.OrderItemAddRequest; import com.cultural.heritage.model.dto.order.OrderItemAddRequest;
import com.cultural.heritage.model.dto.order.OrderQueryRequest; import com.cultural.heritage.model.dto.order.OrderQueryRequest;
import com.cultural.heritage.model.dto.order.capital.GeneralGoodSingleBuyAddRequest;
import com.cultural.heritage.model.dto.order.capital.OrderItemMainInfoAddRequest; import com.cultural.heritage.model.dto.order.capital.OrderItemMainInfoAddRequest;
import com.cultural.heritage.model.dto.order.capital.OrderMainInfoAddRequest; import com.cultural.heritage.model.dto.order.capital.OrderMainInfoAddRequest;
import com.cultural.heritage.model.dto.snapshot.AddressSnapshot; import com.cultural.heritage.model.dto.snapshot.AddressSnapshot;
import com.cultural.heritage.model.dto.snapshot.ContactsSnapshot; import com.cultural.heritage.model.dto.snapshot.ContactsSnapshot;
import com.cultural.heritage.model.dto.snapshot.CouponSnapshot; import com.cultural.heritage.model.dto.snapshot.CouponSnapshot;
import com.cultural.heritage.model.dto.snapshot.GoodSnapshot; import com.cultural.heritage.model.dto.snapshot.GoodSnapshot;
import com.cultural.heritage.model.entity.Coupon;
import com.cultural.heritage.model.entity.Good; import com.cultural.heritage.model.entity.Good;
import com.cultural.heritage.model.entity.Order; import com.cultural.heritage.model.entity.Order;
import com.cultural.heritage.model.entity.OrderItems; import com.cultural.heritage.model.entity.OrderItems;
import com.cultural.heritage.model.enums.GoodTypeEnum; import com.cultural.heritage.model.enums.GoodTypeEnum;
import com.cultural.heritage.service.good.CartRecordService; import com.cultural.heritage.service.good.CartRecordService;
import com.cultural.heritage.service.good.CouponService;
import com.cultural.heritage.service.good.GoodService; import com.cultural.heritage.service.good.GoodService;
import com.cultural.heritage.service.order.OrderItemService; import com.cultural.heritage.service.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService; import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.utils.MultiDelayMessage;
import com.cultural.heritage.utils.OrderNumberUtils; import com.cultural.heritage.utils.OrderNumberUtils;
import com.cultural.heritage.utils.SqlUtils; import com.cultural.heritage.utils.SqlUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -72,6 +78,14 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
private CartRecordService cartRecordService; private CartRecordService cartRecordService;
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private CouponService couponService;
/** /**
* 获取查询条件 * 获取查询条件
*/ */
@ -255,12 +269,12 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
good.setInventory(good.getInventory() - inventory); good.setInventory(good.getInventory() - inventory);
} }
boolean update = goodService.updateBatchById(goodList); boolean update = goodService.updateBatchById(goodList);
ThrowUtils.throwIf(!update, ErrorCode.SYSTEM_ERROR, "商品库存更新失败"); ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "商品库存更新失败");
// 清空购物车 // 清空购物车
if (isCartOrder) { if (isCartOrder) {
boolean remove = cartRecordService.removeBatchByIds(cartIds); boolean remove = cartRecordService.removeBatchByIds(cartIds);
ThrowUtils.throwIf(!remove, ErrorCode.SYSTEM_ERROR, "清空购物车失败"); ThrowUtils.throwIf(!remove, ErrorCode.OPERATION_ERROR, "清空购物车失败");
} }
return id; return id;
} }
@ -271,8 +285,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
* 校验单独购买的商品数据是否准确 * 校验单独购买的商品数据是否准确
*/ */
@Override @Override
public void validSingleGoodOrder(OrderMainInfoAddRequest orderMainInfoAddRequest) { public void validSingleGoodOrder(GeneralGoodSingleBuyAddRequest generalGoodSingleBuyAddRequest) {
List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = orderMainInfoAddRequest.getOrderItemMainInfoAddRequestList(); List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = generalGoodSingleBuyAddRequest.getOrderItemMainInfoAddRequestList();
if (orderItemMainInfoAddRequestList.size() != 1) { if (orderItemMainInfoAddRequestList.size() != 1) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "购买了多种商品或未购买商品"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "购买了多种商品或未购买商品");
} }
@ -293,13 +307,39 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
public BigDecimal calculateTotalAmount(OrderMainInfoAddRequest orderMainInfoAddRequest) { public BigDecimal calculateTotalAmount(OrderMainInfoAddRequest orderMainInfoAddRequest) {
List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = orderMainInfoAddRequest.getOrderItemMainInfoAddRequestList(); List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = orderMainInfoAddRequest.getOrderItemMainInfoAddRequestList();
OrderItemMainInfoAddRequest orderItemMainInfoAddRequest = orderItemMainInfoAddRequestList.get(0); OrderItemMainInfoAddRequest orderItemMainInfoAddRequest = orderItemMainInfoAddRequestList.get(0);
Long couponId = orderMainInfoAddRequest.getCouponId();
Coupon coupon = couponService.getById(couponId);
ThrowUtils.throwIf(coupon == null, ErrorCode.OPERATION_ERROR, "优惠券不存在");
BigDecimal conditionAmount = coupon.getConditionAmount();
Integer quantity = orderItemMainInfoAddRequest.getQuantity(); Integer quantity = orderItemMainInfoAddRequest.getQuantity();
Long goodId = orderItemMainInfoAddRequest.getGoodId(); Long goodId = orderItemMainInfoAddRequest.getGoodId();
Good good = goodService.getById(goodId); Good good = goodService.getById(goodId);
return good.getPrice().multiply(BigDecimal.valueOf(quantity)); return good.getPrice().multiply(BigDecimal.valueOf(quantity)).subtract(conditionAmount);
} }
/**
* 向消息队列中发送订单创建的消息
*/
@Override
public void sendCreateOrderMessage(Long orderId) {
// 延迟检查订单状态信息
MultiDelayMessage<Long> msg = new MultiDelayMessage<>(orderId,
10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L, 60000L, 60000L, 120000L, 300000L, 600000L, 600000L);
// 10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L, 60000L, 60000L, 120000L, 300000L, 600000L, 600000L
int delayValue = msg.removeNextDelay().intValue();
rabbitTemplate.convertAndSend(MqConstant.DELAY_EXCHANGE,
MqConstant.DELAY_ORDER_ROUTING_KEY, msg, message -> {
// 添加延迟消息属性
message.getMessageProperties().setDelay(delayValue);
return message;
});
}
/** /**
* 根据id获取订单(地址联系人优惠券商品)信息 * 根据id获取订单(地址联系人优惠券商品)信息
*/ */

View File

@ -0,0 +1,11 @@
package com.cultural.heritage.service.order.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cultural.heritage.mapper.RefundRecordMapper;
import com.cultural.heritage.model.entity.RefundRecord;
import com.cultural.heritage.service.order.RefundRecordService;
import org.springframework.stereotype.Service;
@Service
public class RefundRecordServiceImpl extends ServiceImpl<RefundRecordMapper, RefundRecord> implements RefundRecordService {
}

View File

@ -35,6 +35,11 @@ public interface WeChatService {
*/ */
Refund refundPayment(String orderId, BigDecimal amount); Refund refundPayment(String orderId, BigDecimal amount);
/**
* 退款申请
*/
Refund refundPartPayment(String orderItemsId, BigDecimal amount);
/** /**
* 获取退款回调信息 * 获取退款回调信息
*/ */

View File

@ -12,6 +12,7 @@ import com.cultural.heritage.config.WxPayConfig;
import com.cultural.heritage.constant.OrderStatusConstant; import com.cultural.heritage.constant.OrderStatusConstant;
import com.cultural.heritage.exception.BusinessException; import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.exception.ThrowUtils; import com.cultural.heritage.exception.ThrowUtils;
import com.cultural.heritage.model.dto.snapshot.CouponSnapshot;
import com.cultural.heritage.model.dto.snapshot.GoodSnapshot; import com.cultural.heritage.model.dto.snapshot.GoodSnapshot;
import com.cultural.heritage.model.entity.Good; import com.cultural.heritage.model.entity.Good;
import com.cultural.heritage.model.entity.Order; import com.cultural.heritage.model.entity.Order;
@ -19,7 +20,9 @@ import com.cultural.heritage.model.entity.OrderItems;
import com.cultural.heritage.service.good.GoodService; import com.cultural.heritage.service.good.GoodService;
import com.cultural.heritage.service.order.OrderItemService; import com.cultural.heritage.service.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService; import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.service.order.RefundRecordService;
import com.cultural.heritage.service.wx.WeChatService; import com.cultural.heritage.service.wx.WeChatService;
import com.cultural.heritage.utils.RefundUtils;
import com.wechat.pay.java.core.notification.NotificationParser; import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam; import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.model.Amount; import com.wechat.pay.java.service.payments.jsapi.model.Amount;
@ -40,6 +43,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -67,6 +71,9 @@ public class WeChatServiceImpl implements WeChatService {
@Resource @Resource
private OrderItemService orderItemService; private OrderItemService orderItemService;
@Resource
private RefundRecordService refundRecordService;
/** /**
* 请求参数 * 请求参数
@ -144,12 +151,14 @@ public class WeChatServiceImpl implements WeChatService {
// 商户订单号 // 商户订单号
createRequest.setOutTradeNo(orderNumber); createRequest.setOutTradeNo(orderNumber);
// 商户退款单号 // 商户退款单号
createRequest.setOutRefundNo(orderNumber); String outRefundNo = RefundUtils.generateRefundNo();
createRequest.setOutRefundNo(outRefundNo);
// 退款结果回调 // 退款结果回调
createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback"); createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback");
// 退款金额 // 退款金额
AmountReq amountReq = new AmountReq(); AmountReq amountReq = new AmountReq();
long refundAmount = amount.movePointRight(2).intValue(); long refundAmount = amount.movePointRight(2).intValue();
amountReq.setRefund(refundAmount); amountReq.setRefund(refundAmount);
amountReq.setTotal(refundAmount); amountReq.setTotal(refundAmount);
amountReq.setCurrency("CNY"); amountReq.setCurrency("CNY");
@ -161,12 +170,60 @@ public class WeChatServiceImpl implements WeChatService {
return refund; return refund;
} }
@Override
public Refund refundPartPayment(String orderItemsId, BigDecimal amount) {
// 获取订单明细
OrderItems orderItems = orderItemService.getById(orderItemsId);
ThrowUtils.throwIf(orderItemsId == null, ErrorCode.OPERATION_ERROR, "订单明细不存在");
// 获取订单明细所属的订单
Order order = orderService.getById(orderItems.getOrderId());
ThrowUtils.throwIf(order == null, ErrorCode.OPERATION_ERROR, "订单不存在");
String orderNumber = order.getOrderNumber();
// 退款请求
CreateRequest createRequest = new CreateRequest();
// 商户订单号
createRequest.setOutTradeNo(orderNumber);
// 商户退款单号
String outRefundNo = RefundUtils.generateRefundNo();
createRequest.setOutRefundNo(outRefundNo);
// 退款结果回调
createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback");
// 退款金额
AmountReq amountReq = new AmountReq();
BigDecimal refundAmount;
CouponSnapshot couponSnapshot = order.getCouponSnapshot();
if (couponSnapshot == null) {
refundAmount = amount;
} else {
BigDecimal couponAmount = couponSnapshot.getConditionAmount();
BigDecimal totalAmount = order.getTotalAmount().add(couponAmount);
BigDecimal itemShare = amount.divide(totalAmount, 10, RoundingMode.HALF_UP);
BigDecimal itemCouponRefund = itemShare.multiply(couponAmount);
refundAmount = amount.subtract(itemCouponRefund);
}
refundAmount = refundAmount.movePointRight(2).setScale(0, RoundingMode.HALF_UP);
amountReq.setRefund(refundAmount.longValue());
amountReq.setTotal(order.getTotalAmount().movePointRight(2).longValue());
amountReq.setCurrency("CNY");
createRequest.setAmount(amountReq);
// 申请退款
System.out.println("退款请求:" + createRequest);
Refund refund = wxPayConfig.getRefundService().create(createRequest);
System.out.println("退款申请结果:" + refund);
return refund;
}
@Override @Override
public RefundNotification getRefundInfo(HttpServletRequest request) { public RefundNotification getRefundInfo(HttpServletRequest request) {
NotificationParser notificationParser = getNotificationParser(request); NotificationParser notificationParser = getNotificationParser(request);
return notificationParser.parse(requestParam, RefundNotification.class); return notificationParser.parse(requestParam, RefundNotification.class);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public synchronized boolean refundCallback(RefundNotification refundNotification) { public synchronized boolean refundCallback(RefundNotification refundNotification) {

View File

@ -0,0 +1,21 @@
package com.cultural.heritage.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class RefundUtils {
// 生成唯一的退款单号格式为 yyyyMMddHHmmssSSS + 随机数
public static String generateRefundNo() {
// 获取当前时间的时间戳
String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
// 生成一个 4 位随机数保证每次退款单号不同
int randomNum = new Random().nextInt(9000) + 1000; // 生成1000到9999之间的随机数
// 拼接退款单号
return timestamp + randomNum;
}
}

View File

@ -94,8 +94,8 @@ wx:
#商户APIv3密钥 #商户APIv3密钥
apiV3Key: fbemuj4Xql7CYlQJAoTEPYxvPSNgYT2t apiV3Key: fbemuj4Xql7CYlQJAoTEPYxvPSNgYT2t
#通知地址 #通知地址
# notifyUrl: https://winning-mouse-internally.ngrok-free.app notifyUrl: https://winning-mouse-internally.ngrok-free.app
notifyUrl: http://123.249.108.160:8888 # notifyUrl: http://123.249.108.160:8888
#微信服务器地址 #微信服务器地址
domain: https://api.mch.weixin.qq.com domain: https://api.mch.weixin.qq.com

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cultural.heritage.model.dto.refund.RefundRecordAddRequest">
</mapper>