trade:优化下单逻辑的实现

This commit is contained in:
YunaiV 2023-09-20 20:21:41 +08:00
parent c766f7daa5
commit 1b477aaa0d
22 changed files with 272 additions and 114 deletions

View File

@ -39,7 +39,7 @@ public interface ProductSkuApi {
List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds); List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds);
/** /**
* 更新 SKU 库存 * 更新 SKU 库存增加 or 减少
* *
* @param updateStockReqDTO 更新请求 * @param updateStockReqDTO 更新请求
*/ */

View File

@ -58,8 +58,9 @@ public interface ErrorCodeConstants {
// ========== Price 相关 1011003000 ============ // ========== Price 相关 1011003000 ============
ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0"); ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0");
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空"); ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空");
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板"); ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板");
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY = new ErrorCode(1011003003, "计算快递运费异常,自提点为空");
// ========== 物流 Express 模块 1011004000 ========== // ========== 物流 Express 模块 1011004000 ==========
ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在"); ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在");

View File

@ -15,7 +15,6 @@ import java.util.Arrays;
@AllArgsConstructor @AllArgsConstructor
public enum DeliveryTypeEnum implements IntArrayValuable { public enum DeliveryTypeEnum implements IntArrayValuable {
NULL(0, "无需物流"),
EXPRESS(1, "快递发货"), EXPRESS(1, "快递发货"),
PICK_UP(2, "用户自提"),; PICK_UP(2, "用户自提"),;

View File

@ -15,7 +15,8 @@ Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}
{ {
"type": 0, "pointStatus": true,
"deliveryType": 1,
"addressId": 21, "addressId": 21,
"items": [ "items": [
{ {

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.trade.controller.app.order; package cn.iocoder.yudao.module.trade.controller.app.order;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
@ -12,11 +11,8 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderOperateTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
@ -65,10 +61,7 @@ public class AppTradeOrderController {
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建订单") @Operation(summary = "创建订单")
@PreAuthenticated @PreAuthenticated
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.TEST) public CommonResult<AppTradeOrderCreateRespVO> createOrder(@Valid @RequestBody AppTradeOrderCreateReqVO createReqVO) {
public CommonResult<AppTradeOrderCreateRespVO> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO) {
TradeOrderLogUtils.setOrderInfo(10L, 1, 2,
MapUtil.<String, Object>builder().put("nickname", "小明").put("thing", "种土豆").build());
TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), getClientIP(), createReqVO); TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), getClientIP(), createReqVO);
return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId())); return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId()));
} }

View File

@ -1,8 +1,11 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo; package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.AssertTrue;
@Schema(description = "用户 App - 交易订单创建 Request VO") @Schema(description = "用户 App - 交易订单创建 Request VO")
@Data @Data
public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
@ -10,4 +13,10 @@ public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
@Schema(description = "备注", example = "这个是我的订单哟") @Schema(description = "备注", example = "这个是我的订单哟")
private String remark; private String remark;
@AssertTrue(message = "配送方式不能为空")
@JsonIgnore
public boolean isDeliveryTypeNotNull() {
return getDeliveryType() != null;
}
} }

View File

@ -30,7 +30,7 @@ public class AppTradeOrderSettlementReqVO {
private Boolean pointStatus; private Boolean pointStatus;
// ========== 配送相关相关字段 ========== // ========== 配送相关相关字段 ==========
@Schema(description = "配送方式", required = true, example = "1") @Schema(description = "配送方式", example = "1")
@InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确") @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确")
private Integer deliveryType; private Integer deliveryType;
@ -62,6 +62,8 @@ public class AppTradeOrderSettlementReqVO {
@Schema(description = "砍价活动编号", example = "123") @Schema(description = "砍价活动编号", example = "123")
private Long bargainActivityId; private Long bargainActivityId;
// TODO @puhui999可以写个参数校验如果 seckillActivityId combinationActivityId combinationHeadId 的情况items 应该只有一个
@Data @Data
@Schema(description = "用户 App - 商品项") @Schema(description = "用户 App - 商品项")
@Valid @Valid

View File

@ -56,6 +56,7 @@ public interface TradeOrderConvert {
@Mappings({ @Mappings({
@Mapping(target = "id", ignore = true), @Mapping(target = "id", ignore = true),
@Mapping(source = "userId", target = "userId"),
@Mapping(source = "createReqVO.couponId", target = "couponId"), @Mapping(source = "createReqVO.couponId", target = "couponId"),
@Mapping(target = "remark", ignore = true), @Mapping(target = "remark", ignore = true),
@Mapping(source = "createReqVO.remark", target = "userRemark"), @Mapping(source = "createReqVO.remark", target = "userRemark"),
@ -89,22 +90,15 @@ public interface TradeOrderConvert {
TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item); TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item);
default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) { default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list)); List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
} new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
return new ProductSkuUpdateStockReqDTO(items);
default ProductSkuUpdateStockReqDTO convertNegative(List<TradeOrderItemDO> list) { }
List<ProductSkuUpdateStockReqDTO.Item> items = TradeOrderConvert.INSTANCE.convertList(list); default ProductSkuUpdateStockReqDTO convertNegative(List<AppTradeOrderSettlementReqVO.Item> list) {
items.forEach(item -> item.setIncrCount(-item.getIncrCount())); List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
return new ProductSkuUpdateStockReqDTO(items); return new ProductSkuUpdateStockReqDTO(items);
} }
List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
@Mappings({
@Mapping(source = "skuId", target = "id"),
@Mapping(source = "count", target = "incrCount"),
})
ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean);
default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems, default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) { TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) {
@ -218,8 +212,9 @@ public interface TradeOrderConvert {
default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO, default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
List<CartDO> cartList) { List<CartDO> cartList) {
TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO(); TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO().setUserId(userId)
reqBO.setUserId(userId).setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId()) .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
.setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
.setItems(new ArrayList<>(settlementReqVO.getItems().size())); .setItems(new ArrayList<>(settlementReqVO.getItems().size()));
// 商品项的构建 // 商品项的构建
Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);

View File

@ -251,12 +251,19 @@ public class TradeOrderDO extends BaseDO {
* 对应 taobao trade.coupon_fee 字段 * 对应 taobao trade.coupon_fee 字段
*/ */
private Integer couponPrice; private Integer couponPrice;
// TODO 芋艿需要记录使用的积分 /**
* 使用的积分
*/
private Integer usePoint;
/** /**
* 积分抵扣的金额单位 * 积分抵扣的金额单位
* *
* 对应 taobao trade.point_fee 字段 * 对应 taobao trade.point_fee 字段
*/ */
private Integer pointPrice; private Integer pointPrice;
// /**
// * 奖励的积分 TODO 疯狂可以使用这个字段哈
// */
// private Integer rewardPoint;
} }

View File

@ -47,6 +47,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.*; import cn.iocoder.yudao.module.trade.enums.order.*;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO; import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService; import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.cart.CartService; import cn.iocoder.yudao.module.trade.service.cart.CartService;
@ -182,32 +183,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE)
public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) { public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
// 1执行订单创建前置处理器 // 0. 价格计算
// TODO @puhui999最好也抽个 beforeOrderCreate 方法不要 BO 各自处理参数岂不美哉
TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
beforeOrderCreateReqBO.setOrderType(validateActivity(createReqVO));
beforeOrderCreateReqBO.setUserId(userId);
beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
// TODO @puhui999这里有个纠结点handler 的定义是只处理指定类型的订单的拓展逻辑还是通用的 handler类似可以处理优惠劵等等
tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
// 2. 价格计算
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
// 1. 订单创建前的逻辑
beforeCreateTradeOrder(userId, createReqVO, calculateRespBO);
// 2.1 插入 TradeOrderDO 订单 // 2.1 插入 TradeOrderDO 订单
TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO); TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO);
// 2.2 插入 TradeOrderItemDO 订单项 // 2.2 插入 TradeOrderItemDO 订单项
List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO); List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
// 3. 订单创建后的逻辑 // 3. 订单创建后的逻辑
afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO); afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
return order; return order;
} }
// TODO @puhui999订单超时自动取消 // TODO @puhui999订单超时自动取消
/** /**
@ -234,9 +227,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
address = validateAddress(userId, createReqVO.getAddressId()); address = validateAddress(userId, createReqVO.getAddressId());
} }
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
String no = orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX); order.setType(calculateRespBO.getType());
order.setType(validateActivity(createReqVO)); order.setNo(orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX));
order.setNo(no);
order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
@ -252,71 +244,80 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
return order; return order;
} }
/** private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO,
* 校验活动并返回订单类型 TradePriceCalculateRespBO calculateRespBO) {
*
* @param createReqVO 请求参数
* @return 订单类型
*/
private Integer validateActivity(AppTradeOrderCreateReqVO createReqVO) {
if (createReqVO.getSeckillActivityId() != null) {
return TradeOrderTypeEnum.SECKILL.getType();
}
if (createReqVO.getCombinationActivityId() != null) {
return TradeOrderTypeEnum.COMBINATION.getType();
}
// TODO 砍价敬请期待
return TradeOrderTypeEnum.NORMAL.getType();
}
private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO); List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
tradeOrderItemMapper.insertBatch(orderItems); tradeOrderItemMapper.insertBatch(orderItems);
return orderItems; return orderItems;
} }
/** /**
* 执行创建完创建完订单后的逻辑 * 订单创建前执行前置逻辑
*
* @param userId 用户编号
* @param createReqVO 创建订单请求
* @param calculateRespBO 订单价格计算结果
*/
private void beforeCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
TradePriceCalculateRespBO calculateRespBO) {
// 1. 执行订单创建前置处理器
TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
beforeOrderCreateReqBO.setOrderType(calculateRespBO.getType());
beforeOrderCreateReqBO.setUserId(userId);
beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
// TODO @puhui999这里有个纠结点handler 的定义是只处理指定类型的订单的拓展逻辑还是通用的 handler类似可以处理优惠劵等等
tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
// 2. 下单时扣减商品库存
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(createReqVO.getItems()));
}
/**
* 订单创建后执行后置逻辑
* *
* 例如说优惠劵的扣减积分的扣减支付单的创建等等 * 例如说优惠劵的扣减积分的扣减支付单的创建等等
* *
* @param userId 用户编号 * @param userId 用户编号
* @param createReqVO 创建订单请求 * @param createReqVO 创建订单请求
* @param tradeOrderDO 交易订单 * @param order 交易订单
* @param calculateRespBO 订单价格计算结果 * @param calculateRespBO 订单价格计算结果
*/ */
private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems, TradeOrderDO order, List<TradeOrderItemDO> orderItems,
TradePriceCalculateRespBO calculateRespBO) { TradePriceCalculateRespBO calculateRespBO) {
// 执行订单创建后置处理器 // 1. 执行订单创建后置处理器
tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(TradeOrderConvert.INSTANCE.convert(userId, createReqVO, tradeOrderDO, orderItems.get(0)))); // TODO @puhui999从通用性来说应该不用 orderItems.get(0)
tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(
TradeOrderConvert.INSTANCE.convert(userId, createReqVO, order, orderItems.get(0))));
// 扣减积分 TODO 芋艿待实现需要前置 // 2. 有使用优惠券时更新
// 这个是不是应该放到支付成功之后如果支付后的话可能积分可以重复使用哈资源类都要预扣 // 不在前置扣减的原因是因为优惠劵要记录使用的订单号
// 有使用优惠券时更新 TODO 芋艿需要前置
if (createReqVO.getCouponId() != null) { if (createReqVO.getCouponId() != null) {
couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId) couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId)
.setOrderId(tradeOrderDO.getId())); .setOrderId(order.getId()));
} }
// 下单时扣减商品库存 // 3. 扣减积分
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems)); // 不在前置扣减的原因是因为积分扣减时需要记录关联业务
if (order.getUsePoint() != null && order.getUsePoint() > 0) {
memberPointApi.reducePoint(userId, calculateRespBO.getUsePoint(),
MemberPointBizTypeEnum.ORDER_USE.getType(), String.valueOf(order.getId()));
}
// 删除购物车商品 // 4. 删除购物车商品
Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId); Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
if (CollUtil.isNotEmpty(cartIds)) { if (CollUtil.isNotEmpty(cartIds)) {
cartService.deleteCart(userId, cartIds); cartService.deleteCart(userId, cartIds);
} }
// 生成预支付 // 5. 生成预支付
createPayOrder(tradeOrderDO, orderItems, calculateRespBO); createPayOrder(order, orderItems, calculateRespBO);
// 增加订单日志 TODO 芋艿待实现 // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
} }
private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) { TradePriceCalculateRespBO calculateRespBO) {
// 创建支付单用于后续的支付 // 创建支付单用于后续的支付
PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert( PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
order, orderItems, calculateRespBO, tradeOrderProperties); order, orderItems, calculateRespBO, tradeOrderProperties);
@ -344,6 +345,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
} }
// 校验活动 // 校验活动
// 1拼团活动 // 1拼团活动
// TODO @puhui999这块也抽象到 handler
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
// 更新拼团状态 TODO puhui999订单支付失败或订单支付过期删除这条拼团记录 // 更新拼团状态 TODO puhui999订单支付失败或订单支付过期删除这条拼团记录
combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now()); combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now());
@ -741,6 +743,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
couponApi.returnUsedCoupon(order.getCouponId()); couponApi.returnUsedCoupon(order.getCouponId());
// 4.回滚积分积分是支付成功后才增加的吧 回复每个项目不同目前看下来确认收货貌似更合适我再看看其它项目的业务选择 // 4.回滚积分积分是支付成功后才增加的吧 回复每个项目不同目前看下来确认收货貌似更合适我再看看其它项目的业务选择
// TODO @疯狂有赞是可配置支付 or 确认收货我们按照支付好列然后这里的退积分指的是下单时的积分抵扣
// TODO 芋艿OrderLog // TODO 芋艿OrderLog
@ -773,17 +776,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Async @Async
protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) { protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) {
int bizType = MemberPointBizTypeEnum.ORDER_BUY.getType(); // TODO @疯狂具体多少积分需要分成 2 不分1. 支付金额2. 商品金额
int bizType = MemberPointBizTypeEnum.ORDER_REWARD.getType();
memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId)); memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId));
} }
@Async @Async
protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) { protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) {
// TODO @疯狂退款时按照金额比例退还积分https://help.youzan.com/displaylist/detail_4_4-1-49185
int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType(); int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType();
memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId)); memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
} }
@Async @Async
protected void addBrokerageAsync(Long userId, Long orderId) { protected void addBrokerageAsync(Long userId, Long orderId) {
MemberUserRespDTO user = memberUserApi.getUser(userId); MemberUserRespDTO user = memberUserApi.getUser(userId);

View File

@ -37,6 +37,11 @@ public class TradeCombinationHandler implements TradeOrderHandler {
@Override @Override
public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) { public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
// TODO @puhui999需要判断下
if (true) {
return;
}
// 创建砍价记录 // 创建砍价记录
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO)); combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO));
} }

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.trade.service.price.bo; package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import lombok.Data; import lombok.Data;
import javax.validation.Valid; import javax.validation.Valid;
@ -17,13 +16,6 @@ import java.util.List;
@Data @Data
public class TradePriceCalculateReqBO { public class TradePriceCalculateReqBO {
/**
* 订单类型
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer type;
/** /**
* 用户编号 * 用户编号
* *
@ -37,13 +29,12 @@ public class TradePriceCalculateReqBO {
* 对应 CouponDO id 编号 * 对应 CouponDO id 编号
*/ */
private Long couponId; private Long couponId;
// TODO @疯狂需要增加一个 PriceCalculator 实现积分扣减的计算写回到 TradePriceCalculateRespBO usePoint
/** /**
* 收货地址编号 * 是否使用积分
*
* 对应 MemberAddressDO id 编号
*/ */
private Long addressId; @NotNull(message = "是否使用积分不能为空")
private Boolean pointStatus;
/** /**
* 配送方式 * 配送方式
@ -51,6 +42,18 @@ public class TradePriceCalculateReqBO {
* 枚举 {@link DeliveryTypeEnum} * 枚举 {@link DeliveryTypeEnum}
*/ */
private Integer deliveryType; private Integer deliveryType;
/**
* 收货地址编号
*
* 对应 MemberAddressDO id 编号
*/
private Long addressId;
/**
* 自提门店编号
*
* 对应 PickUpStoreDO id 编号
*/
private Long pickUpStoreId;
/** /**
* 商品 SKU 数组 * 商品 SKU 数组
@ -58,6 +61,30 @@ public class TradePriceCalculateReqBO {
@NotNull(message = "商品数组不能为空") @NotNull(message = "商品数组不能为空")
private List<Item> items; private List<Item> items;
// ========== 秒杀活动相关字段 ==========
/**
* 秒杀活动编号
*/
private Long seckillActivityId;
// ========== 拼团活动相关字段 ==========
// TODO @puhui999是不是拼团记录的编号哈
/**
* 拼团活动编号
*/
private Long combinationActivityId;
/**
* 拼团团长编号
*/
private Long combinationHeadId;
// ========== 砍价活动相关字段 ==========
/**
* 砍价活动编号
*/
private Long bargainActivityId;
/** /**
* 商品 SKU * 商品 SKU
*/ */

View File

@ -48,6 +48,11 @@ public class TradePriceCalculateRespBO {
*/ */
private Long couponId; private Long couponId;
/**
* 使用的积分
*/
private Integer usePoint;
/** /**
* 订单价格 * 订单价格
*/ */

View File

@ -2,11 +2,14 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.member.api.address.AddressApi; import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@ -22,8 +25,7 @@ import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND;
/** /**
* 运费的 {@link TradePriceCalculator} 实现类 * 运费的 {@link TradePriceCalculator} 实现类
@ -37,20 +39,41 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
@Resource @Resource
private AddressApi addressApi; private AddressApi addressApi;
@Resource
private DeliveryPickUpStoreService deliveryPickUpStoreService;
@Resource @Resource
private DeliveryExpressTemplateService deliveryExpressTemplateService; private DeliveryExpressTemplateService deliveryExpressTemplateService;
@Override @Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// TODO @芋艿如果门店自提需要校验是否开启 if (param.getDeliveryType() == null) {
// 1.1 判断配送方式
if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
return; return;
} }
if (param.getAddressId() == null) { if (DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY); calculateByPickUp(param, result);
} else if (DeliveryTypeEnum.EXPRESS.getMode().equals(param.getDeliveryType())) {
calculateExpress(param, result);
}
}
private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
if (param.getPickUpStoreId() == null) {
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
}
DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId());
if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) {
throw exception(PICK_UP_STORE_NOT_EXISTS);
}
}
// ========= 快递发货 ==========
private void calculateExpress(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1. 得到收件地址区域
if (param.getAddressId() == null) {
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY);
} }
// 1.2 得到收件地址区域
AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId()); AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
@ -89,8 +112,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
for (OrderItem orderItem : orderItems) { for (OrderItem orderItem : orderItems) {
totalCount += orderItem.getCount(); totalCount += orderItem.getCount();
totalPrice += orderItem.getPayPrice(); totalPrice += orderItem.getPayPrice();
totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount(); if (orderItem.getWeight() != null) {
totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount(); totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
}
if (orderItem.getVolume() != null) {
totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
}
} }
// 优先判断是否包邮. 如果包邮不计算快递运费 // 优先判断是否包邮. 如果包邮不计算快递运费
if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight, if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 使用积分的 {@link TradePriceCalculator} 实现类
*
* @author owen
*/
@Component
@Order(TradePriceCalculator.ORDER_POINT_USE)
@Slf4j
public class TradePointUsePriceCalculator implements TradePriceCalculator {
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// TODO 疯狂待实现嘿嘿
if (param.getPointStatus()) {
result.setUsePoint(10);
} else {
result.setUsePoint(0);
}
}
}

View File

@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
/** /**
* 价格计算的计算器接口 * 价格计算的计算器接口
* *
* 优惠计算顺序
* 1. <a href="https://help.youzan.com/displaylist/detail_4_4-1-53316">积分抵现会员价优惠券粉丝专享价满减送哪个优先计算</>
*
* @author 芋道源码 * @author 芋道源码
*/ */
public interface TradePriceCalculator { public interface TradePriceCalculator {
@ -13,12 +16,13 @@ public interface TradePriceCalculator {
int ORDER_DISCOUNT_ACTIVITY = 10; int ORDER_DISCOUNT_ACTIVITY = 10;
int ORDER_REWARD_ACTIVITY = 20; int ORDER_REWARD_ACTIVITY = 20;
int ORDER_COUPON = 30; int ORDER_COUPON = 30;
int ORDER_POINT_USE = 40;
/** /**
* 快递运费的计算 * 快递运费的计算
* *
* 放在各种营销活动优惠劵后面 TODO * 放在各种营销活动优惠劵后面 TODO
*/ */
int ORDER_DELIVERY = 40; int ORDER_DELIVERY = 50;
void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result); void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result);

View File

@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@ -28,7 +29,7 @@ public class TradePriceCalculatorHelper {
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) { List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
// 创建 PriceCalculateRespDTO 对象 // 创建 PriceCalculateRespDTO 对象
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
result.setType(param.getType()); result.setType(getOrderType(param));
result.setPromotions(new ArrayList<>()); result.setPromotions(new ArrayList<>());
// 创建它的 OrderItem 属性 // 创建它的 OrderItem 属性
@ -69,6 +70,23 @@ public class TradePriceCalculatorHelper {
return result; return result;
} }
/**
* 计算订单类型
*
* @param param 计算参数
* @return 订单类型
*/
private static Integer getOrderType(TradePriceCalculateReqBO param) {
if (param.getSeckillActivityId() != null) {
return TradeOrderTypeEnum.SECKILL.getType();
}
if (param.getCombinationActivityId() != null) {
return TradeOrderTypeEnum.COMBINATION.getType();
}
// TODO 砍价敬请期待
return TradeOrderTypeEnum.NORMAL.getType();
}
/** /**
* 基于订单项重新计算 price 总价 * 基于订单项重新计算 price 总价
* *

View File

@ -45,7 +45,7 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
public void testCalculatePrice() { public void testCalculatePrice() {
// 准备参数 // 准备参数
TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO() TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO()
.setType(TradeOrderTypeEnum.NORMAL.getType()).setUserId(10L) .setUserId(10L)
.setCouponId(20L).setAddressId(30L) .setCouponId(20L).setAddressId(30L)
.setItems(Arrays.asList( .setItems(Arrays.asList(
new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true), new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true),

View File

@ -21,6 +21,13 @@
<groupId>cn.iocoder.boot</groupId> <groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId> <artifactId>yudao-common</artifactId>
</dependency> </dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.api.point;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import javax.validation.constraints.Min;
/** /**
* 用户积分的 API 接口 * 用户积分的 API 接口
* *
@ -17,6 +19,18 @@ public interface MemberPointApi {
* @param bizType 业务类型 {@link MemberPointBizTypeEnum} * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
* @param bizId 业务编号 * @param bizId 业务编号
*/ */
void addPoint(Long userId, Integer point, Integer bizType, String bizId); void addPoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
Integer bizType, String bizId);
/**
* 减少用户积分
*
* @param userId 用户编号
* @param point 积分
* @param bizType 业务类型 {@link MemberPointBizTypeEnum}
* @param bizId 业务编号
*/
void reducePoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
Integer bizType, String bizId);
} }

View File

@ -17,8 +17,10 @@ import java.util.Objects;
public enum MemberPointBizTypeEnum implements IntArrayValuable { public enum MemberPointBizTypeEnum implements IntArrayValuable {
SIGN(1, "签到", "签到获得 {} 积分", true), SIGN(1, "签到", "签到获得 {} 积分", true),
ORDER_BUY(10, "订单消费", "下单获得 {} 积分", true), ORDER_REWARD(10, "订单奖励", "下单获得 {} 积分", true),
ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false); // 退回积分 ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false), // 退回积分
ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 扣减积分
;
/** /**
* 类型 * 类型

View File

@ -31,4 +31,13 @@ public class MemberPointApiImpl implements MemberPointApi {
memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId); memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
} }
@Override
public void reducePoint(Long userId, Integer point, Integer bizType, String bizId) {
MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType);
if (bizTypeEnum == null) {
throw exception(POINT_RECORD_BIZ_NOT_SUPPORT);
}
memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
}
} }