更新了优惠券模块

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;
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.update.UpdateWrapper;
@ -11,7 +9,6 @@ import com.cultural.heritage.annotation.AuthCheck;
import com.cultural.heritage.common.BaseResponse;
import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.common.ResultUtils;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.constant.UserConstant;
import com.cultural.heritage.exception.BusinessException;
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.UserCouponService;
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.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@ -65,8 +60,6 @@ public class CouponController {
private UserCouponService userCouponService;
@Resource
private RabbitTemplate rabbitTemplate;
/**
@ -100,18 +93,8 @@ public class CouponController {
boolean result = couponService.save(coupon);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "优惠券添加失败");
// 延迟检查优惠券是否过期
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;
});
// 向消息队列中发送优惠券创建的消息
couponService.sendCouponCreateMessage(coupon);
return ResultUtils.success(true);
}
@ -251,18 +234,8 @@ public class CouponController {
boolean save = userCouponService.save(userCoupon);
ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "兑换记录插入失败");
// 延迟检查优惠券是否过期
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;
});
// 向消息队列中发送用户优惠券创建的消息
couponService.sendUserCouponCreateMessage(userCoupon, coupon);
// 更新用户积分
loginUser.setPoints(loginUser.getPoints() - requirePoints);
@ -388,8 +361,8 @@ public class CouponController {
if (usableCouponQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
BigDecimal conditionAmount = usableCouponQueryRequest.getConditionAmount();
if (conditionAmount.compareTo(BigDecimal.ZERO) <= 0) {
BigDecimal currentAmount = usableCouponQueryRequest.getCurrentAmount();
if (currentAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "订单金额参数错误");
}
User loginUser = userService.getLoginUser(request);
@ -403,9 +376,9 @@ public class CouponController {
// 获取()可使用的优惠券
List<UserCoupon> userCoupons = userCouponList.stream().filter(userCoupon -> {
CouponVO couponVO = userCoupon.getCouponVO();
BigDecimal couponAmount = couponVO.getConditionAmount();
BigDecimal standardAmount = couponVO.getStandardAmount();
Boolean isAvailable = usableCouponQueryRequest.getIsAvailable();
int result = conditionAmount.compareTo(couponAmount);
int result = currentAmount.compareTo(standardAmount);
return isAvailable == (result >= 0);
}).toList();

View File

@ -7,7 +7,6 @@ import com.cultural.heritage.annotation.AuthCheck;
import com.cultural.heritage.common.BaseResponse;
import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.common.ResultUtils;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.constant.OrderStatusConstant;
import com.cultural.heritage.constant.UserConstant;
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.order.OrderQueryRequest;
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.OrderMainInfoAddRequest;
import com.cultural.heritage.model.dto.snapshot.GoodSnapshot;
import com.cultural.heritage.model.entity.*;
import com.cultural.heritage.model.vo.order.OrderVO;
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.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService;
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.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
@ -78,9 +77,10 @@ public class OrderController {
private GoodService goodService;
@Resource
private RabbitTemplate rabbitTemplate;
private CouponService couponService;
@ -98,14 +98,14 @@ public class OrderController {
User loginUser = userService.getLoginUser(request);
Long userId = loginUser.getId();
// 封装成订单创建请求体
OrderMainInfoAddRequest orderMainInfoAddRequest = new OrderMainInfoAddRequest();
BeanUtils.copyProperties(cartOrderAddRequest, orderMainInfoAddRequest);
// 校验购物车数据是否准确
List<CartOrderItemAddRequest> cartOrderItemAddRequestList = cartOrderAddRequest.getCartOrderItemAddRequestList();
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列表
List<Long> cartIds = cartOrderItemAddRequestList.stream().map(CartOrderItemAddRequest::getCartRecordId).toList();
@ -140,6 +140,11 @@ public class OrderController {
Integer quantity = map.get(cartRecordId);
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);
// 封装成订单明细主要信息请求体列表
@ -157,17 +162,8 @@ public class OrderController {
// 创建通用订单(常规类和服务类商品)
Long orderId = orderService.createCommonOrder(orderMainInfoAddRequest, userId, true, cartIds);
// 延迟检查订单状态信息
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;
});
// 向消息队列中发送订单创建的消息
orderService.sendCreateOrderMessage(orderId);
return ResultUtils.success(orderId);
}
@ -176,38 +172,36 @@ public class OrderController {
/**
* 小程序端用户创建常规类商品订单
* @param orderMainInfoAddRequest 订单创建请求体
* @param generalGoodSingleBuyAddRequest 常规类类商品单独购买创建请求体
* @return 是否创建成功
*/
@PostMapping("/add")
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "小程序端用户创建常规类商品订单", description = "参数订单创建请求体权限所有人方法名addOrder")
public BaseResponse<Long> addOrder(@RequestBody OrderMainInfoAddRequest orderMainInfoAddRequest, HttpServletRequest request) {
if (orderMainInfoAddRequest == null) {
public BaseResponse<Long> addOrder(@RequestBody GeneralGoodSingleBuyAddRequest generalGoodSingleBuyAddRequest, HttpServletRequest request) {
if (generalGoodSingleBuyAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 校验用户当前是否处于登录态
User loginUser = userService.getLoginUser(request);
Long userId = loginUser.getId();
// 校验单独购买的商品数据是否准确
orderService.validSingleGoodOrder(orderMainInfoAddRequest);
orderService.validSingleGoodOrder(generalGoodSingleBuyAddRequest);
// 封装成订单主要信息请求体
OrderMainInfoAddRequest orderMainInfoAddRequest = new OrderMainInfoAddRequest();
BeanUtils.copyProperties(generalGoodSingleBuyAddRequest, orderMainInfoAddRequest);
// 计算单个商品购买的总金额
BigDecimal totalAmount = orderService.calculateTotalAmount(orderMainInfoAddRequest);
orderMainInfoAddRequest.setTotalAmount(totalAmount);
// 创建通用订单(常规类和服务类商品)
Long orderId = orderService.createCommonOrder(orderMainInfoAddRequest, userId, false, null);
// 延迟检查订单状态信息
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;
});
// 向消息队列中发送订单创建的消息
orderService.sendCreateOrderMessage(orderId);
return ResultUtils.success(orderId);
}
@ -282,11 +276,31 @@ public class OrderController {
queryWrapper.eq("userId", id);
queryWrapper.orderByDesc("id");
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 -> {
Long orderId = order.getId();
QueryWrapper<OrderItems> orderItemsQueryWrapper = new QueryWrapper<>();
orderItemsQueryWrapper.eq("orderId", orderId);
List<OrderItems> orderItemsList = orderItemService.list(orderItemsQueryWrapper);
List<OrderItems> orderItemsList = map.get(orderId);
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(order, orderVO);
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.model.dto.CommonRequest;
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.service.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.service.user.UserService;
import com.cultural.heritage.service.wx.WeChatService;
@ -46,6 +48,10 @@ public class WeChatPayController {
@Resource
private WeChatService weChatService;
@Resource
private OrderItemService orderItemService;
/**
* JSAPI 下单
@ -83,10 +89,14 @@ public class WeChatPayController {
/**
* 退款仅管理员
* @param commonRequest 订单id
*/
@PostMapping("/refund/create")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Refund> createRefund(@RequestBody CommonRequest commonRequest) {
if (commonRequest == null || commonRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Long orderId = commonRequest.getId();
Order order = ordersService.getById(orderId);
ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在");
@ -94,6 +104,26 @@ public class WeChatPayController {
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
@Schema(description = "优惠券添加请求体", requiredProperties =
{"name", "conditionAmount", "requirePoints", "content", "startTime", "endTime", "description"})
{"name", "standardAmount", "conditionAmount", "requirePoints", "content", "startTime", "endTime", "description"})
public class CouponAddRequest implements Serializable {
@ -19,6 +19,14 @@ public class CouponAddRequest implements Serializable {
@Schema(description = "优惠券名称", example = "满200减50")
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")
private String name;
/**
* 标准金额
*/
@Schema(description = "标准金额", example = "200")
private BigDecimal standardAmount;
/**
* 满减金额
*/

View File

@ -9,7 +9,7 @@ import java.math.BigDecimal;
@Data
@Schema(description = "可用优惠券请求体", requiredProperties =
{"conditionAmount", "isAvailable"})
{"standardAmount", "isAvailable"})
public class UsableCouponQueryRequest implements Serializable {
@ -17,7 +17,7 @@ public class UsableCouponQueryRequest implements Serializable {
* 订单金额
*/
@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 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 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.cultural.heritage.model.dto.coupon.CouponQueryRequest;
import com.cultural.heritage.model.entity.Coupon;
import com.cultural.heritage.model.entity.UserCoupon;
public interface CouponService extends IService<Coupon> {
@ -24,4 +25,17 @@ public interface CouponService extends IService<Coupon> {
* 校验
*/
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;
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.extension.service.impl.ServiceImpl;
import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.constant.CommonConstant;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.mapper.CouponMapper;
import com.cultural.heritage.model.dto.coupon.CouponQueryRequest;
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.utils.MultiDelayMessage;
import com.cultural.heritage.utils.SqlUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@ -21,6 +29,10 @@ import java.util.Date;
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {
@Resource
private RabbitTemplate rabbitTemplate;
@Override
public QueryWrapper<Coupon> getQueryWrapper(CouponQueryRequest couponQueryRequest) {
if (couponQueryRequest == null) {
@ -57,6 +69,7 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
Long id = coupon.getId();
String name = coupon.getName();
BigDecimal conditionAmount = coupon.getConditionAmount();
BigDecimal standardAmount = coupon.getStandardAmount();
Integer requirePoints = coupon.getRequirePoints();
String startTime = coupon.getStartTime();
String endTime = coupon.getEndTime();
@ -69,9 +82,16 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
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) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "满减价格错误");
}
if (conditionAmount.compareTo(standardAmount) > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "满减价格不能超过标准价格");
}
if (ObjectUtils.isEmpty(requirePoints) || requirePoints <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "积分参数错误");
}
@ -82,4 +102,46 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
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.cultural.heritage.model.dto.order.OrderAddRequest;
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.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);
/**
* 向消息队列中发送订单创建的消息
*/
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.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.constant.CommonConstant;
import com.cultural.heritage.constant.MqConstant;
import com.cultural.heritage.constant.OrderStatusConstant;
import com.cultural.heritage.exception.BusinessException;
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.OrderItemAddRequest;
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.OrderMainInfoAddRequest;
import com.cultural.heritage.model.dto.snapshot.AddressSnapshot;
import com.cultural.heritage.model.dto.snapshot.ContactsSnapshot;
import com.cultural.heritage.model.dto.snapshot.CouponSnapshot;
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.Order;
import com.cultural.heritage.model.entity.OrderItems;
import com.cultural.heritage.model.enums.GoodTypeEnum;
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.order.OrderItemService;
import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.utils.MultiDelayMessage;
import com.cultural.heritage.utils.OrderNumberUtils;
import com.cultural.heritage.utils.SqlUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@ -72,6 +78,14 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
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);
}
boolean update = goodService.updateBatchById(goodList);
ThrowUtils.throwIf(!update, ErrorCode.SYSTEM_ERROR, "商品库存更新失败");
ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "商品库存更新失败");
// 清空购物车
if (isCartOrder) {
boolean remove = cartRecordService.removeBatchByIds(cartIds);
ThrowUtils.throwIf(!remove, ErrorCode.SYSTEM_ERROR, "清空购物车失败");
ThrowUtils.throwIf(!remove, ErrorCode.OPERATION_ERROR, "清空购物车失败");
}
return id;
}
@ -271,8 +285,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
* 校验单独购买的商品数据是否准确
*/
@Override
public void validSingleGoodOrder(OrderMainInfoAddRequest orderMainInfoAddRequest) {
List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = orderMainInfoAddRequest.getOrderItemMainInfoAddRequestList();
public void validSingleGoodOrder(GeneralGoodSingleBuyAddRequest generalGoodSingleBuyAddRequest) {
List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = generalGoodSingleBuyAddRequest.getOrderItemMainInfoAddRequestList();
if (orderItemMainInfoAddRequestList.size() != 1) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "购买了多种商品或未购买商品");
}
@ -293,13 +307,39 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
public BigDecimal calculateTotalAmount(OrderMainInfoAddRequest orderMainInfoAddRequest) {
List<OrderItemMainInfoAddRequest> orderItemMainInfoAddRequestList = orderMainInfoAddRequest.getOrderItemMainInfoAddRequestList();
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();
Long goodId = orderItemMainInfoAddRequest.getGoodId();
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获取订单(地址联系人优惠券商品)信息
*/

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 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.exception.BusinessException;
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.entity.Good;
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.order.OrderItemService;
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.utils.RefundUtils;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
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.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -67,6 +71,9 @@ public class WeChatServiceImpl implements WeChatService {
@Resource
private OrderItemService orderItemService;
@Resource
private RefundRecordService refundRecordService;
/**
* 请求参数
@ -144,12 +151,14 @@ public class WeChatServiceImpl implements WeChatService {
// 商户订单号
createRequest.setOutTradeNo(orderNumber);
// 商户退款单号
createRequest.setOutRefundNo(orderNumber);
String outRefundNo = RefundUtils.generateRefundNo();
createRequest.setOutRefundNo(outRefundNo);
// 退款结果回调
createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback");
// 退款金额
AmountReq amountReq = new AmountReq();
long refundAmount = amount.movePointRight(2).intValue();
amountReq.setRefund(refundAmount);
amountReq.setTotal(refundAmount);
amountReq.setCurrency("CNY");
@ -161,12 +170,60 @@ public class WeChatServiceImpl implements WeChatService {
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
public RefundNotification getRefundInfo(HttpServletRequest request) {
NotificationParser notificationParser = getNotificationParser(request);
return notificationParser.parse(requestParam, RefundNotification.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
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密钥
apiV3Key: fbemuj4Xql7CYlQJAoTEPYxvPSNgYT2t
#通知地址
# notifyUrl: https://winning-mouse-internally.ngrok-free.app
notifyUrl: http://123.249.108.160:8888
notifyUrl: https://winning-mouse-internally.ngrok-free.app
# notifyUrl: http://123.249.108.160:8888
#微信服务器地址
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>