Ver Fonte

trade:完成下单时,创建支付单逻辑

YunaiV há 2 anos atrás
pai
commit
5934d6b029

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -1,24 +1,31 @@
 package cn.iocoder.yudao.module.trade.convert.order;
 
+import cn.hutool.core.date.DateUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
 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.enums.order.TradeOrderItemRefundStatusEnum;
+import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
+import java.time.Duration;
 import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
 
 @Mapper
 public interface TradeOrderConvert {
@@ -67,4 +74,20 @@ public interface TradeOrderConvert {
     ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean);
     List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
 
+    default PayOrderInfoCreateReqDTO convert(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
+                                             List<ProductSpuRespDTO> spus, TradeOrderProperties tradeOrderProperties) {
+        PayOrderInfoCreateReqDTO createReqDTO = new PayOrderInfoCreateReqDTO()
+                .setAppId(tradeOrderProperties.getAppId()).setUserIp(tradeOrderDO.getUserIp());
+        // 商户相关字段
+        createReqDTO.setMerchantOrderId(String.valueOf(tradeOrderDO.getId()));
+        String subject = spus.get(0).getName();
+        if (spus.size() > 1) {
+            subject += " 等多件";
+        }
+        createReqDTO.setSubject(subject);
+        // 订单相关字段
+        createReqDTO.setAmount(tradeOrderDO.getPayPrice()).setExpireTime(addTime(tradeOrderProperties.getExpireTime()));
+        return createReqDTO;
+    }
+
 }

+ 0 - 30
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/pay/PayOrderConvert.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.trade.convert.pay;
-
-import cn.hutool.core.date.DateUtil;
-import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.Mappings;
-import org.mapstruct.Named;
-import org.mapstruct.factory.Mappers;
-
-import java.util.Date;
-
-@Mapper
-public interface PayOrderConvert {
-
-    PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class);
-
-    @Mappings({
-            @Mapping(source = "payPrice", target = "amount"),
-            @Mapping(target = "expireTime", source = "cancelTime" , qualifiedByName = "convertCreateTimeToPayExpireTime")
-    })
-    PayOrderInfoCreateReqDTO convert(TradeOrderDO tradeOrderDO);
-
-    @Named("convertCreateTimeToPayExpireTime")
-    default Date convertCreateTimeToPayExpireTime(Date cancelTime) {
-        return DateUtil.offsetMinute(new Date(), 30);
-    }
-    
-}

+ 9 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java

@@ -5,8 +5,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.validation.annotation.Validated;
 
 import javax.validation.constraints.NotNull;
+import java.time.Duration;
 
 /**
+ * 交易订单的配置项
+ *
  * @author LeeYan9
  * @since 2022-09-15
  */
@@ -15,16 +18,16 @@ import javax.validation.constraints.NotNull;
 @Validated
 public class TradeOrderProperties {
 
-    /**
-     * 商户订单编号
-     */
-    @NotNull(message = "商户订单编号不能为空")
-    private String merchantOrderId;
-
     /**
      * 应用编号
      */
     @NotNull(message = "应用编号不能为空")
     private Long appId;
 
+    /**
+     * 支付超时时间
+     */
+    @NotNull(message = "支付超时时间不能为空")
+    private Duration expireTime;
+
 }

+ 16 - 59
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java

@@ -1,13 +1,10 @@
 package cn.iocoder.yudao.module.trade.service.order;
 
-import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.text.StrBuilder;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
@@ -30,7 +27,6 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
 import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
@@ -58,10 +54,6 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREAT
 @Service
 public class TradeOrderServiceImpl implements TradeOrderService {
 
-    // TODO LeeYan9: 静态变量, 需要在最前面哈; 另外, 静态变量的注释最好写下;
-    private static final String BLANK_PLACEHOLDER = " ";
-    private static final String MULTIPLIER_PLACEHOLDER = "x";
-
     @Resource
     private TradeOrderMapper tradeOrderMapper;
     @Resource
@@ -89,7 +81,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         // 商品 SKU 检查:可售状态、库存
         List<ProductSkuRespDTO> skus = validateSkuSaleable(createReqVO.getItems());
         // 商品 SPU 检查:可售状态
-        validateSpuSaleable(convertSet(skus, ProductSkuRespDTO::getSpuId));
+        List<ProductSpuRespDTO> spus = validateSpuSaleable(convertSet(skus, ProductSkuRespDTO::getSpuId));
         // 用户收件地址的校验
         AddressRespDTO address = validateAddress(userId, createReqVO.getAddressId());
 
@@ -102,55 +94,11 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         List<TradeOrderItemDO> tradeOrderItems = createTradeOrderItems(tradeOrderDO, priceResp.getOrder().getItems(), skus);
 
         // 订单创建完后的逻辑
-        afterCreateTradeOrder(userId, createReqVO, tradeOrderDO, tradeOrderItems);
+        afterCreateTradeOrder(userId, createReqVO, tradeOrderDO, tradeOrderItems, spus);
         // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
         return tradeOrderDO.getId();
     }
 
-    private void fillPayOrderInfoFromItems(PayOrderInfoCreateReqDTO payOrderInfoCreateReqDTO,
-                                           List<TradeOrderItemDO> tradeOrderItems) {
-        // 填写 商品&应用信息
-        payOrderInfoCreateReqDTO.setMerchantOrderId(tradeOrderProperties.getMerchantOrderId());
-        payOrderInfoCreateReqDTO.setAppId(tradeOrderProperties.getAppId());
-
-        // 填写商品信息
-        StrBuilder subject = new StrBuilder();
-        StrBuilder body = new StrBuilder();
-        for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
-            // append subject
-            subject.append(BLANK_PLACEHOLDER);
-            subject.append(tradeOrderItem.getName());
-            // append body
-            body.append(BLANK_PLACEHOLDER);
-            body.append(tradeOrderItem.getName());
-            body.append(MULTIPLIER_PLACEHOLDER);
-            body.append(tradeOrderItem.getCount());
-        }
-        // 设置 subject & body
-        // TODO @LeeYan9: 可以抽象一个 StrUtils 方法; 或者看看 hutool 有没自带的哈
-        payOrderInfoCreateReqDTO.setSubject(StrUtils.maxLength(subject.subString(1), 32));
-        payOrderInfoCreateReqDTO.setBody(StrUtils.maxLength(body.subString(1), 128));
-    }
-
-    private void xfillItemsInfoFromSkuAndOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItems,
-                                              Map<Long, ProductSkuRespDTO> spuInfos) {
-        for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
-            // 填充订单信息
-            tradeOrderItem.setOrderId(tradeOrderDO.getId());
-            tradeOrderItem.setUserId(tradeOrderDO.getUserId());
-            // 填充SKU信息
-            ProductSkuRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId());
-            tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId());
-            tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl());
-            tradeOrderItem.setName(skuInfoRespDTO.getName());
-            tradeOrderItem.setRefundStatus(TradeOrderItemRefundStatusEnum.NONE.getStatus());
-            // todo
-            List<TradeOrderItemDO.Property> property =
-                    BeanUtil.copyToList(skuInfoRespDTO.getProperties(), TradeOrderItemDO.Property.class);
-            tradeOrderItem.setProperties(property);
-        }
-    }
-
     /**
      * 校验商品 SKU 是否可出售
      *
@@ -248,7 +196,8 @@ public class TradeOrderServiceImpl implements TradeOrderService {
      * @param tradeOrderDO 交易订单
      */
     private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
-                                       TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs) {
+                                       TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
+                                       List<ProductSpuRespDTO> spus) {
         // 下单时扣减商品库存
         productSkuApi.updateSkuStock(new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(tradeOrderItemDOs)));
 
@@ -262,13 +211,21 @@ public class TradeOrderServiceImpl implements TradeOrderService {
                     .setOrderId(tradeOrderDO.getId()));
         }
 
-        // 构建预支付请求参数
-        // TODO @LeeYan9: 需要更新到订单上
-//        PayOrderInfoCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
-//        fillPayOrderInfoFromItems(payOrderCreateReqDTO, tradeOrderItems);
         // 生成预支付
+        createPayOrder(tradeOrderDO, tradeOrderItemDOs, spus);
 
         // 增加订单日志 TODO 芋艿:待实现
     }
 
+    private void createPayOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
+                                List<ProductSpuRespDTO> spus) {
+        // 创建支付单,用于后续的支付
+        PayOrderInfoCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
+                tradeOrderDO, tradeOrderItemDOs, spus, tradeOrderProperties);
+        Long payOrderId = payOrderApi.createPayOrder(payOrderCreateReqDTO);
+
+        // 更新到交易单上
+        tradeOrderMapper.updateById(new TradeOrderDO().setId(tradeOrderDO.getId()).setPayOrderId(payOrderId));
+    }
+
 }

+ 25 - 2
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java

@@ -25,12 +25,15 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig;
+import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentMatcher;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.List;
 
@@ -73,6 +76,15 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
     @MockBean
     private CouponApi couponApi;
 
+    @MockBean
+    private TradeOrderProperties tradeOrderProperties;
+
+    @BeforeEach
+    public void setUp() {
+        when(tradeOrderProperties.getAppId()).thenReturn(888L);
+        when(tradeOrderProperties.getExpireTime()).thenReturn(Duration.ofDays(1));
+    }
+
     @Test
     public void testCreateTradeOrder_success() {
         // 准备参数
@@ -92,7 +104,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
         when(productSkuApi.getSkuList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(sku01, sku02));
         // mock 方法(商品 SPU 检查)
         ProductSpuRespDTO spu01 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(11L)
-                .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()));
+                .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()).setName("商品 1"));
         ProductSpuRespDTO spu02 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(21L)
                 .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()));
         when(productSpuApi.getSpuList(eq(asSet(11L, 21L)))).thenReturn(Arrays.asList(spu01, spu02));
@@ -120,6 +132,17 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
             assertEquals(priceCalculateReqDTO.getItems().get(1).getCount(), 4);
             return true;
         }))).thenReturn(new PriceCalculateRespDTO().setOrder(priceOrder));
+        // mock 方法(创建支付单)
+        when(payOrderApi.createPayOrder(argThat(createReqDTO -> {
+            assertEquals(createReqDTO.getAppId(), 888L);
+            assertEquals(createReqDTO.getUserIp(), userIp);
+            assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空
+            assertEquals(createReqDTO.getSubject(), "商品 1 等多件");
+            assertNull(createReqDTO.getBody());
+            assertEquals(createReqDTO.getAmount(), 80);
+            assertNotNull(createReqDTO.getExpireTime());
+            return true;
+        }))).thenReturn(1000L);
 
         // 调用方法
         Long tradeOrderId = tradeOrderService.createTradeOrder(userId, userIp, reqVO);
@@ -147,7 +170,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
         assertEquals(tradeOrderDO.getDiscountPrice(), 0);
         assertEquals(tradeOrderDO.getAdjustPrice(), 0);
         assertEquals(tradeOrderDO.getPayPrice(), 80);
-        assertNull(tradeOrderDO.getPayOrderId());
+        assertEquals(tradeOrderDO.getPayOrderId(), 1000L);
         assertNull(tradeOrderDO.getPayChannel());
         assertNull(tradeOrderDO.getDeliveryTemplateId());
         assertNull(tradeOrderDO.getExpressNo());

+ 4 - 5
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderInfoCreateReqDTO.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.pay.api.order;
 import lombok.Data;
 import org.hibernate.validator.constraints.Length;
 
-import javax.validation.constraints.DecimalMin;
+import javax.validation.constraints.Min;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.io.Serializable;
@@ -45,7 +45,7 @@ public class PayOrderInfoCreateReqDTO implements Serializable {
     /**
      * 商品描述
      */
-    @NotEmpty(message = "商品描述信息不能为空")
+//    @NotEmpty(message = "商品描述信息不能为空") // 允许空
     @Length(max = 128, message = "商品描述信息长度不能超过128")
     private String body;
 
@@ -55,8 +55,7 @@ public class PayOrderInfoCreateReqDTO implements Serializable {
      * 支付金额,单位:分
      */
     @NotNull(message = "支付金额不能为空")
-    // TODO @LeeYan9: 是不是 @Min 注解呀, 是 Integer 哈
-    @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
+    @Min(value = 1, message = "支付金额必须大于零")
     private Integer amount;
 
     /**
@@ -65,4 +64,4 @@ public class PayOrderInfoCreateReqDTO implements Serializable {
     @NotNull(message = "支付过期时间不能为空")
     private Date expireTime;
 
-}
+}

+ 2 - 2
yudao-server/src/main/resources/application.yaml

@@ -97,8 +97,8 @@ yudao:
     enable: true # 验证码的开关,默认为 true;注意,优先读取数据库 infra_config 的 yudao.captcha.enable,所以请从数据库修改,可能需要重启项目
   trade:
     order:
-      app-id: 1
-      merchant-order-id: 1
+      app-id: 1 # 商户编号
+      expire-time: 2h # 支付的过期时间
   codegen:
     base-package: ${yudao.info.base-package}
     db-schemas: ${spring.datasource.dynamic.datasource.master.name}