Commit 1b477aaa0d46985efff9f8891c762e54d7bf9c00

Authored by YunaiV
1 parent c766f7da

trade:优化下单逻辑的实现

Showing 22 changed files with 268 additions and 110 deletions
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
... ... @@ -39,7 +39,7 @@ public interface ProductSkuApi {
39 39 List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds);
40 40  
41 41 /**
42   - * 更新 SKU 库存
  42 + * 更新 SKU 库存(增加 or 减少)
43 43 *
44 44 * @param updateStockReqDTO 更新请求
45 45 */
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -58,8 +58,9 @@ public interface ErrorCodeConstants {
58 58  
59 59 // ========== Price 相关 1011003000 ============
60 60 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0");
61   - ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空");
  61 + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空");
62 62 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板");
  63 + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY = new ErrorCode(1011003003, "计算快递运费异常,自提点为空");
63 64  
64 65 // ========== 物流 Express 模块 1011004000 ==========
65 66 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在");
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryTypeEnum.java
... ... @@ -15,7 +15,6 @@ import java.util.Arrays;
15 15 @AllArgsConstructor
16 16 public enum DeliveryTypeEnum implements IntArrayValuable {
17 17  
18   - NULL(0, "无需物流"),
19 18 EXPRESS(1, "快递发货"),
20 19 PICK_UP(2, "用户自提"),;
21 20  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.http
... ... @@ -15,7 +15,8 @@ Authorization: Bearer {{appToken}}
15 15 tenant-id: {{appTenentId}}
16 16  
17 17 {
18   - "type": 0,
  18 + "pointStatus": true,
  19 + "deliveryType": 1,
19 20 "addressId": 21,
20 21 "items": [
21 22 {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
1 1 package cn.iocoder.yudao.module.trade.controller.app.order;
2 2  
3   -import cn.hutool.core.map.MapUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
... ... @@ -12,11 +11,8 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
12 11 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
13 12 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
14 13 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
15   -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderOperateTypeEnum;
16 14 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
17 15 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
18   -import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
19   -import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
20 16 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
21 17 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
22 18 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
... ... @@ -65,10 +61,7 @@ public class AppTradeOrderController {
65 61 @PostMapping("/create")
66 62 @Operation(summary = "创建订单")
67 63 @PreAuthenticated
68   - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.TEST)
69   - public CommonResult<AppTradeOrderCreateRespVO> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO) {
70   - TradeOrderLogUtils.setOrderInfo(10L, 1, 2,
71   - MapUtil.<String, Object>builder().put("nickname", "小明").put("thing", "种土豆").build());
  64 + public CommonResult<AppTradeOrderCreateRespVO> createOrder(@Valid @RequestBody AppTradeOrderCreateReqVO createReqVO) {
72 65 TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), getClientIP(), createReqVO);
73 66 return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId()));
74 67 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
1 1 package cn.iocoder.yudao.module.trade.controller.app.order.vo;
2 2  
  3 +import com.fasterxml.jackson.annotation.JsonIgnore;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.Data;
5 6  
  7 +import javax.validation.constraints.AssertTrue;
  8 +
6 9 @Schema(description = "用户 App - 交易订单创建 Request VO")
7 10 @Data
8 11 public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
... ... @@ -10,4 +13,10 @@ public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
10 13 @Schema(description = "备注", example = "这个是我的订单哟")
11 14 private String remark;
12 15  
  16 + @AssertTrue(message = "配送方式不能为空")
  17 + @JsonIgnore
  18 + public boolean isDeliveryTypeNotNull() {
  19 + return getDeliveryType() != null;
  20 + }
  21 +
13 22 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
... ... @@ -30,7 +30,7 @@ public class AppTradeOrderSettlementReqVO {
30 30 private Boolean pointStatus;
31 31  
32 32 // ========== 配送相关相关字段 ==========
33   - @Schema(description = "配送方式", required = true, example = "1")
  33 + @Schema(description = "配送方式", example = "1")
34 34 @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确")
35 35 private Integer deliveryType;
36 36  
... ... @@ -62,6 +62,8 @@ public class AppTradeOrderSettlementReqVO {
62 62 @Schema(description = "砍价活动编号", example = "123")
63 63 private Long bargainActivityId;
64 64  
  65 + // TODO @puhui999:可以写个参数校验,如果 seckillActivityId 或 combinationActivityId 或 combinationHeadId 的情况,items 应该只有一个
  66 +
65 67 @Data
66 68 @Schema(description = "用户 App - 商品项")
67 69 @Valid
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
... ... @@ -56,6 +56,7 @@ public interface TradeOrderConvert {
56 56  
57 57 @Mappings({
58 58 @Mapping(target = "id", ignore = true),
  59 + @Mapping(source = "userId", target = "userId"),
59 60 @Mapping(source = "createReqVO.couponId", target = "couponId"),
60 61 @Mapping(target = "remark", ignore = true),
61 62 @Mapping(source = "createReqVO.remark", target = "userRemark"),
... ... @@ -89,23 +90,16 @@ public interface TradeOrderConvert {
89 90 TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item);
90 91  
91 92 default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
92   - return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list));
  93 + List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
  94 + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
  95 + return new ProductSkuUpdateStockReqDTO(items);
93 96 }
94   -
95   - default ProductSkuUpdateStockReqDTO convertNegative(List<TradeOrderItemDO> list) {
96   - List<ProductSkuUpdateStockReqDTO.Item> items = TradeOrderConvert.INSTANCE.convertList(list);
97   - items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
  97 + default ProductSkuUpdateStockReqDTO convertNegative(List<AppTradeOrderSettlementReqVO.Item> list) {
  98 + List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
  99 + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
98 100 return new ProductSkuUpdateStockReqDTO(items);
99 101 }
100 102  
101   - List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
102   -
103   - @Mappings({
104   - @Mapping(source = "skuId", target = "id"),
105   - @Mapping(source = "count", target = "incrCount"),
106   - })
107   - ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean);
108   -
109 103 default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
110 104 TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) {
111 105 PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO()
... ... @@ -218,8 +212,9 @@ public interface TradeOrderConvert {
218 212  
219 213 default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
220 214 List<CartDO> cartList) {
221   - TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO();
222   - reqBO.setUserId(userId).setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId())
  215 + TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO().setUserId(userId)
  216 + .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
  217 + .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
223 218 .setItems(new ArrayList<>(settlementReqVO.getItems().size()));
224 219 // 商品项的构建
225 220 Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
... ... @@ -251,12 +251,19 @@ public class TradeOrderDO extends BaseDO {
251 251 * 对应 taobao 的 trade.coupon_fee 字段
252 252 */
253 253 private Integer couponPrice;
254   - // TODO 芋艿:需要记录使用的积分;
  254 + /**
  255 + * 使用的积分
  256 + */
  257 + private Integer usePoint;
255 258 /**
256 259 * 积分抵扣的金额,单位:分
257 260 *
258 261 * 对应 taobao 的 trade.point_fee 字段
259 262 */
260 263 private Integer pointPrice;
  264 +// /**
  265 +// * 奖励的积分 TODO 疯狂:可以使用这个字段哈;
  266 +// */
  267 +// private Integer rewardPoint;
261 268  
262 269 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -47,6 +47,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
47 47 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
48 48 import cn.iocoder.yudao.module.trade.enums.order.*;
49 49 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
  50 +import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
50 51 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
51 52 import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
52 53 import cn.iocoder.yudao.module.trade.service.cart.CartService;
... ... @@ -182,32 +183,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
182 183  
183 184 @Override
184 185 @Transactional(rollbackFor = Exception.class)
  186 + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE)
185 187 public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
186   - // 1、执行订单创建前置处理器
187   - // TODO @puhui999:最好也抽个 beforeOrderCreate 方法;不要 BO 各自处理参数岂不美哉?
188   - TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
189   - beforeOrderCreateReqBO.setOrderType(validateActivity(createReqVO));
190   - beforeOrderCreateReqBO.setUserId(userId);
191   - beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
192   - // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
193   - tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
194   -
195   - // 2. 价格计算
  188 + // 0. 价格计算
196 189 TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
197 190  
  191 + // 1. 订单创建前的逻辑
  192 + beforeCreateTradeOrder(userId, createReqVO, calculateRespBO);
  193 +
198 194 // 2.1 插入 TradeOrderDO 订单
199 195 TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO);
200 196 // 2.2 插入 TradeOrderItemDO 订单项
201 197 List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
202 198  
203   - // 3. 订单创建完后的逻辑
  199 + // 3. 订单创建后的逻辑
204 200 afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
205   -
206   - // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
207 201 return order;
208 202 }
209 203  
210   -
211 204 // TODO @puhui999:订单超时,自动取消;
212 205  
213 206 /**
... ... @@ -234,9 +227,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
234 227 address = validateAddress(userId, createReqVO.getAddressId());
235 228 }
236 229 TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
237   - String no = orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX);
238   - order.setType(validateActivity(createReqVO));
239   - order.setNo(no);
  230 + order.setType(calculateRespBO.getType());
  231 + order.setNo(orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX));
240 232 order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
241 233 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
242 234 order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
... ... @@ -252,71 +244,80 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
252 244 return order;
253 245 }
254 246  
  247 + private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO,
  248 + TradePriceCalculateRespBO calculateRespBO) {
  249 + List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
  250 + tradeOrderItemMapper.insertBatch(orderItems);
  251 + return orderItems;
  252 + }
  253 +
255 254 /**
256   - * 校验活动,并返回订单类型
  255 + * 订单创建前,执行前置逻辑
257 256 *
258   - * @param createReqVO 请求参数
259   - * @return 订单类型
  257 + * @param userId 用户编号
  258 + * @param createReqVO 创建订单请求
  259 + * @param calculateRespBO 订单价格计算结果
260 260 */
261   - private Integer validateActivity(AppTradeOrderCreateReqVO createReqVO) {
262   - if (createReqVO.getSeckillActivityId() != null) {
263   - return TradeOrderTypeEnum.SECKILL.getType();
264   - }
265   - if (createReqVO.getCombinationActivityId() != null) {
266   - return TradeOrderTypeEnum.COMBINATION.getType();
267   - }
268   - // TODO 砍价敬请期待
269   - return TradeOrderTypeEnum.NORMAL.getType();
270   - }
  261 + private void beforeCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
  262 + TradePriceCalculateRespBO calculateRespBO) {
  263 + // 1. 执行订单创建前置处理器
  264 + TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
  265 + beforeOrderCreateReqBO.setOrderType(calculateRespBO.getType());
  266 + beforeOrderCreateReqBO.setUserId(userId);
  267 + beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
  268 + // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
  269 + tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
271 270  
272   - private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
273   - List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
274   - tradeOrderItemMapper.insertBatch(orderItems);
275   - return orderItems;
  271 + // 2. 下单时扣减商品库存
  272 + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(createReqVO.getItems()));
276 273 }
277 274  
278 275 /**
279   - * 执行创建完创建完订单后的逻辑
  276 + * 订单创建后,执行后置逻辑
280 277 *
281 278 * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
282 279 *
283 280 * @param userId 用户编号
284 281 * @param createReqVO 创建订单请求
285   - * @param tradeOrderDO 交易订单
  282 + * @param order 交易订单
286 283 * @param calculateRespBO 订单价格计算结果
287 284 */
288 285 private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
289   - TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
  286 + TradeOrderDO order, List<TradeOrderItemDO> orderItems,
290 287 TradePriceCalculateRespBO calculateRespBO) {
291   - // 执行订单创建后置处理器
292   - tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(TradeOrderConvert.INSTANCE.convert(userId, createReqVO, tradeOrderDO, orderItems.get(0))));
293   -
294   - // 扣减积分 TODO 芋艿:待实现,需要前置;
295   - // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
  288 + // 1. 执行订单创建后置处理器
  289 + // TODO @puhui999:从通用性来说,应该不用 orderItems.get(0)
  290 + tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(
  291 + TradeOrderConvert.INSTANCE.convert(userId, createReqVO, order, orderItems.get(0))));
296 292  
297   - // 有使用优惠券时更新 TODO 芋艿:需要前置;
  293 + // 2. 有使用优惠券时更新
  294 + // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
298 295 if (createReqVO.getCouponId() != null) {
299 296 couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId)
300   - .setOrderId(tradeOrderDO.getId()));
  297 + .setOrderId(order.getId()));
301 298 }
302 299  
303   - // 下单时扣减商品库存
304   - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
  300 + // 3. 扣减积分
  301 + // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
  302 + if (order.getUsePoint() != null && order.getUsePoint() > 0) {
  303 + memberPointApi.reducePoint(userId, calculateRespBO.getUsePoint(),
  304 + MemberPointBizTypeEnum.ORDER_USE.getType(), String.valueOf(order.getId()));
  305 + }
305 306  
306   - // 删除购物车商品
  307 + // 4. 删除购物车商品
307 308 Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
308 309 if (CollUtil.isNotEmpty(cartIds)) {
309 310 cartService.deleteCart(userId, cartIds);
310 311 }
311 312  
312   - // 生成预支付
313   - createPayOrder(tradeOrderDO, orderItems, calculateRespBO);
  313 + // 5. 生成预支付
  314 + createPayOrder(order, orderItems, calculateRespBO);
314 315  
315   - // 增加订单日志 TODO 芋艿:待实现
  316 + // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
316 317 }
317 318  
318   -
319   - private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) {
  319 + private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
  320 + TradePriceCalculateRespBO calculateRespBO) {
320 321 // 创建支付单,用于后续的支付
321 322 PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
322 323 order, orderItems, calculateRespBO, tradeOrderProperties);
... ... @@ -344,6 +345,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
344 345 }
345 346 // 校验活动
346 347 // 1、拼团活动
  348 + // TODO @puhui999:这块也抽象到 handler 里
347 349 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
348 350 // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
349 351 combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now());
... ... @@ -741,6 +743,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
741 743 couponApi.returnUsedCoupon(order.getCouponId());
742 744  
743 745 // 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择;
  746 + // TODO @疯狂:有赞是可配置(支付 or 确认收货),我们按照支付好列;然后这里的退积分,指的是下单时的积分抵扣。
744 747  
745 748 // TODO 芋艿:OrderLog
746 749  
... ... @@ -773,17 +776,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
773 776  
774 777 @Async
775 778 protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) {
776   - int bizType = MemberPointBizTypeEnum.ORDER_BUY.getType();
  779 + // TODO @疯狂:具体多少积分,需要分成 2 不分:1. 支付金额;2. 商品金额
  780 + int bizType = MemberPointBizTypeEnum.ORDER_REWARD.getType();
777 781 memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId));
778 782 }
779 783  
780 784 @Async
781 785 protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) {
  786 + // TODO @疯狂:退款时,按照金额比例,退还积分;https://help.youzan.com/displaylist/detail_4_4-1-49185
782 787 int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType();
783 788 memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
784 789 }
785 790  
786   -
787 791 @Async
788 792 protected void addBrokerageAsync(Long userId, Long orderId) {
789 793 MemberUserRespDTO user = memberUserApi.getUser(userId);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
... ... @@ -37,6 +37,11 @@ public class TradeCombinationHandler implements TradeOrderHandler {
37 37  
38 38 @Override
39 39 public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
  40 + // TODO @puhui999:需要判断下;
  41 + if (true) {
  42 + return;
  43 + }
  44 +
40 45 // 创建砍价记录
41 46 combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO));
42 47 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
1 1 package cn.iocoder.yudao.module.trade.service.price.bo;
2 2  
3 3 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
4   -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
5 4 import lombok.Data;
6 5  
7 6 import javax.validation.Valid;
... ... @@ -18,13 +17,6 @@ import java.util.List;
18 17 public class TradePriceCalculateReqBO {
19 18  
20 19 /**
21   - * 订单类型
22   - *
23   - * 枚举 {@link TradeOrderTypeEnum}
24   - */
25   - private Integer type;
26   -
27   - /**
28 20 * 用户编号
29 21 *
30 22 * 对应 MemberUserDO 的 id 编号
... ... @@ -37,20 +29,31 @@ public class TradePriceCalculateReqBO {
37 29 * 对应 CouponDO 的 id 编号
38 30 */
39 31 private Long couponId;
  32 + // TODO @疯狂:需要增加一个 PriceCalculator 实现积分扣减的计算;写回到 TradePriceCalculateRespBO 的 usePoint
  33 + /**
  34 + * 是否使用积分
  35 + */
  36 + @NotNull(message = "是否使用积分不能为空")
  37 + private Boolean pointStatus;
40 38  
41 39 /**
  40 + * 配送方式
  41 + *
  42 + * 枚举 {@link DeliveryTypeEnum}
  43 + */
  44 + private Integer deliveryType;
  45 + /**
42 46 * 收货地址编号
43 47 *
44 48 * 对应 MemberAddressDO 的 id 编号
45 49 */
46 50 private Long addressId;
47   -
48 51 /**
49   - * 配送方式
  52 + * 自提门店编号
50 53 *
51   - * 枚举 {@link DeliveryTypeEnum}
  54 + * 对应 PickUpStoreDO 的 id 编号
52 55 */
53   - private Integer deliveryType;
  56 + private Long pickUpStoreId;
54 57  
55 58 /**
56 59 * 商品 SKU 数组
... ... @@ -58,6 +61,30 @@ public class TradePriceCalculateReqBO {
58 61 @NotNull(message = "商品数组不能为空")
59 62 private List<Item> items;
60 63  
  64 + // ========== 秒杀活动相关字段 ==========
  65 + /**
  66 + * 秒杀活动编号
  67 + */
  68 + private Long seckillActivityId;
  69 +
  70 + // ========== 拼团活动相关字段 ==========
  71 + // TODO @puhui999:是不是拼团记录的编号哈?
  72 + /**
  73 + * 拼团活动编号
  74 + */
  75 + private Long combinationActivityId;
  76 +
  77 + /**
  78 + * 拼团团长编号
  79 + */
  80 + private Long combinationHeadId;
  81 +
  82 + // ========== 砍价活动相关字段 ==========
  83 + /**
  84 + * 砍价活动编号
  85 + */
  86 + private Long bargainActivityId;
  87 +
61 88 /**
62 89 * 商品 SKU
63 90 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
... ... @@ -49,6 +49,11 @@ public class TradePriceCalculateRespBO {
49 49 private Long couponId;
50 50  
51 51 /**
  52 + * 使用的积分
  53 + */
  54 + private Integer usePoint;
  55 +
  56 + /**
52 57 * 订单价格
53 58 */
54 59 @Data
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
... ... @@ -2,11 +2,14 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.module.member.api.address.AddressApi;
6 7 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
7 9 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
8 10 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
9 11 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
  12 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
10 13 import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO;
11 14 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
12 15 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
... ... @@ -22,8 +25,7 @@ import java.util.Set;
22 25  
23 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
25   -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY;
26   -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND;
  28 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
27 29  
28 30 /**
29 31 * 运费的 {@link TradePriceCalculator} 实现类
... ... @@ -37,20 +39,41 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
37 39  
38 40 @Resource
39 41 private AddressApi addressApi;
  42 +
  43 + @Resource
  44 + private DeliveryPickUpStoreService deliveryPickUpStoreService;
40 45 @Resource
41 46 private DeliveryExpressTemplateService deliveryExpressTemplateService;
42 47  
43 48 @Override
44 49 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
45   - // TODO @芋艿:如果门店自提,需要校验是否开启;
46   - // 1.1 判断配送方式
47   - if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
  50 + if (param.getDeliveryType() == null) {
48 51 return;
49 52 }
  53 + if (DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
  54 + calculateByPickUp(param, result);
  55 + } else if (DeliveryTypeEnum.EXPRESS.getMode().equals(param.getDeliveryType())) {
  56 + calculateExpress(param, result);
  57 + }
  58 + }
  59 +
  60 + private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  61 + if (param.getPickUpStoreId() == null) {
  62 + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
  63 + }
  64 + DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId());
  65 + if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) {
  66 + throw exception(PICK_UP_STORE_NOT_EXISTS);
  67 + }
  68 + }
  69 +
  70 + // ========= 快递发货 ==========
  71 +
  72 + private void calculateExpress(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  73 + // 1. 得到收件地址区域
50 74 if (param.getAddressId() == null) {
51   - throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY);
  75 + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY);
52 76 }
53   - // 1.2 得到收件地址区域
54 77 AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
55 78 Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
56 79  
... ... @@ -89,8 +112,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
89 112 for (OrderItem orderItem : orderItems) {
90 113 totalCount += orderItem.getCount();
91 114 totalPrice += orderItem.getPayPrice();
92   - totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
93   - totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
  115 + if (orderItem.getWeight() != null) {
  116 + totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
  117 + }
  118 + if (orderItem.getVolume() != null) {
  119 + totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
  120 + }
94 121 }
95 122 // 优先判断是否包邮. 如果包邮不计算快递运费
96 123 if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.price.calculator;
  2 +
  3 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  4 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.core.annotation.Order;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 使用积分的 {@link TradePriceCalculator} 实现类
  11 + *
  12 + * @author owen
  13 + */
  14 +@Component
  15 +@Order(TradePriceCalculator.ORDER_POINT_USE)
  16 +@Slf4j
  17 +public class TradePointUsePriceCalculator implements TradePriceCalculator {
  18 +
  19 + @Override
  20 + public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  21 + // TODO 疯狂:待实现,嘿嘿;
  22 + if (param.getPointStatus()) {
  23 + result.setUsePoint(10);
  24 + } else {
  25 + result.setUsePoint(0);
  26 + }
  27 + }
  28 +
  29 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java
... ... @@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
6 6 /**
7 7 * 价格计算的计算器接口
8 8 *
  9 + * 优惠计算顺序:
  10 + * 1. <a href="https://help.youzan.com/displaylist/detail_4_4-1-53316">积分抵现、会员价、优惠券、粉丝专享价、满减送哪个优先计算?</>
  11 + *
9 12 * @author 芋道源码
10 13 */
11 14 public interface TradePriceCalculator {
... ... @@ -13,12 +16,13 @@ public interface TradePriceCalculator {
13 16 int ORDER_DISCOUNT_ACTIVITY = 10;
14 17 int ORDER_REWARD_ACTIVITY = 20;
15 18 int ORDER_COUPON = 30;
  19 + int ORDER_POINT_USE = 40;
16 20 /**
17 21 * 快递运费的计算
18 22 *
19 23 * 放在各种营销活动、优惠劵后面 TODO
20 24 */
21   - int ORDER_DELIVERY = 40;
  25 + int ORDER_DELIVERY = 50;
22 26  
23 27 void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result);
24 28  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 5 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
6 6 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  7 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
7 8 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
8 9 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
9 10  
... ... @@ -28,7 +29,7 @@ public class TradePriceCalculatorHelper {
28 29 List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
29 30 // 创建 PriceCalculateRespDTO 对象
30 31 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
31   - result.setType(param.getType());
  32 + result.setType(getOrderType(param));
32 33 result.setPromotions(new ArrayList<>());
33 34  
34 35 // 创建它的 OrderItem 属性
... ... @@ -70,6 +71,23 @@ public class TradePriceCalculatorHelper {
70 71 }
71 72  
72 73 /**
  74 + * 计算订单类型
  75 + *
  76 + * @param param 计算参数
  77 + * @return 订单类型
  78 + */
  79 + private static Integer getOrderType(TradePriceCalculateReqBO param) {
  80 + if (param.getSeckillActivityId() != null) {
  81 + return TradeOrderTypeEnum.SECKILL.getType();
  82 + }
  83 + if (param.getCombinationActivityId() != null) {
  84 + return TradeOrderTypeEnum.COMBINATION.getType();
  85 + }
  86 + // TODO 砍价敬请期待
  87 + return TradeOrderTypeEnum.NORMAL.getType();
  88 + }
  89 +
  90 + /**
73 91 * 基于订单项,重新计算 price 总价
74 92 *
75 93 * @param result 计算结果
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java
... ... @@ -45,7 +45,7 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
45 45 public void testCalculatePrice() {
46 46 // 准备参数
47 47 TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO()
48   - .setType(TradeOrderTypeEnum.NORMAL.getType()).setUserId(10L)
  48 + .setUserId(10L)
49 49 .setCouponId(20L).setAddressId(30L)
50 50 .setItems(Arrays.asList(
51 51 new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true),
... ...
yudao-module-member/yudao-module-member-api/pom.xml
... ... @@ -21,6 +21,13 @@
21 21 <groupId>cn.iocoder.boot</groupId>
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
  24 +
  25 + <!-- 参数校验 -->
  26 + <dependency>
  27 + <groupId>org.springframework.boot</groupId>
  28 + <artifactId>spring-boot-starter-validation</artifactId>
  29 + <optional>true</optional>
  30 + </dependency>
24 31 </dependencies>
25 32  
26 33 </project>
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.api.point;
2 2  
3 3 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
4 4  
  5 +import javax.validation.constraints.Min;
  6 +
5 7 /**
6 8 * 用户积分的 API 接口
7 9 *
... ... @@ -17,6 +19,18 @@ public interface MemberPointApi {
17 19 * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
18 20 * @param bizId 业务编号
19 21 */
20   - void addPoint(Long userId, Integer point, Integer bizType, String bizId);
  22 + void addPoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
  23 + Integer bizType, String bizId);
  24 +
  25 + /**
  26 + * 减少用户积分
  27 + *
  28 + * @param userId 用户编号
  29 + * @param point 积分
  30 + * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
  31 + * @param bizId 业务编号
  32 + */
  33 + void reducePoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
  34 + Integer bizType, String bizId);
21 35  
22 36 }
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
... ... @@ -17,8 +17,10 @@ import java.util.Objects;
17 17 public enum MemberPointBizTypeEnum implements IntArrayValuable {
18 18  
19 19 SIGN(1, "签到", "签到获得 {} 积分", true),
20   - ORDER_BUY(10, "订单消费", "下单获得 {} 积分", true),
21   - ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false); // 退回积分
  20 + ORDER_REWARD(10, "订单奖励", "下单获得 {} 积分", true),
  21 + ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false), // 退回积分
  22 + ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 扣减积分
  23 + ;
22 24  
23 25 /**
24 26 * 类型
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
... ... @@ -31,4 +31,13 @@ public class MemberPointApiImpl implements MemberPointApi {
31 31 memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
32 32 }
33 33  
  34 + @Override
  35 + public void reducePoint(Long userId, Integer point, Integer bizType, String bizId) {
  36 + MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType);
  37 + if (bizTypeEnum == null) {
  38 + throw exception(POINT_RECORD_BIZ_NOT_SUPPORT);
  39 + }
  40 + memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
  41 + }
  42 +
34 43 }
... ...