Преглед изворни кода

!262 接口实现-交易订单创建(未完全实现)
Merge pull request !262 from LeeYan9/ly_uniapp

芋道源码 пре 2 година
родитељ
комит
61b0624a59
23 измењених фајлова са 831 додато и 4 уклоњено
  1. 76 0
      sql/optional/mall/order.sql
  2. 1 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
  3. 30 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
  4. 45 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java
  5. 93 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuInfoRespDTO.java
  6. 23 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  7. 124 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/SpuInfoRespDTO.java
  8. 23 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/enums/ErrorCodeConstants.java
  9. 13 0
      yudao-module-mall/yudao-module-trade-biz/pom.xml
  10. 18 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
  11. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
  12. 20 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  13. 33 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderItemConvert.java
  14. 17 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/pay/PayOrderConvert.java
  15. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/price/PriceConvert.java
  16. 20 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/sku/ProductSkuConvert.java
  17. 11 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
  18. 11 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java
  19. 19 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java
  20. 138 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
  21. 7 0
      yudao-module-pay/yudao-module-pay-api/pom.xml
  22. 20 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java
  23. 65 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderDataCreateReqDTO.java

+ 76 - 0
sql/optional/mall/order.sql

@@ -0,0 +1,76 @@
+/**todo cancelType 设置默认值 0?*/
+CREATE TABLE `trade_order`
+(
+    `id`                      bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `sn`                      varchar(32)     NOT NULL COMMENT '订单流水号',
+    `type`                    int             NOT NULL DEFAULT '0' COMMENT '订单类型:[0:普通订单 1:秒杀订单 2:拼团订单 3:砍价订单]',
+    `terminal`                int             NOT NULL COMMENT '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]',
+    `user_id`                 bigint unsigned NOT NULL COMMENT '用户编号',
+    `user_ip`                 varchar(30)     NOT NULL DEFAULT '' COMMENT '用户 IP',
+    `user_remark`             varchar(200)             DEFAULT NULL COMMENT '用户备注',
+    `status`                  int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:待付款 1:待发货 2:待收货 3:已完成 4:已关闭]',
+    `product_count`           int             NOT NULL COMMENT '购买的商品数量',
+    `cancel_type`             int             NOT NULL COMMENT '取消类型:[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
+    `remark`                  varchar(200)             DEFAULT NULL COMMENT '商家备注',
+    `payed`                   bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]',
+    `finish_time`             datetime                 DEFAULT NULL COMMENT '订单完成时间',
+    `cancel_time`             datetime                 DEFAULT NULL COMMENT '订单取消时间',
+    `sku_original_price`      int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
+    `sku_promotion_price`     int             NOT NULL DEFAULT '0' COMMENT '商品优惠(总),单位:分',
+    `order_promotion_price`   int             NOT NULL DEFAULT '0' COMMENT '订单优惠(总),单位:分',
+    `delivery_price`          int             NOT NULL DEFAULT '0' COMMENT '运费金额,单位:分',
+    `pay_price`               int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
+    `pay_order_id`            int             NOT NULL COMMENT '支付订单编号',
+    `pay_channel`             int             NOT NULL COMMENT '支付成功的支付渠道',
+    `delivery_type`           int             NOT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
+    `actual_delivery_type`    int             NOT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
+    `delivery_templateid`     int                      DEFAULT NULL COMMENT '配置模板的编号',
+    `express_no`              int                      DEFAULT NULL COMMENT '物流公司单号',
+    `delivery_status`         bit(1)          NOT NULL DEFAULT b'0' COMMENT '发货状态[0:未发货 1:已发货]',
+    `delivery_time`           datetime                 DEFAULT NULL COMMENT '发货时间',
+    `receive_time`            datetime                 DEFAULT NULL COMMENT '收货时间',
+    `receiver_name`           varchar(20)     NOT NULL COMMENT '收件人名称',
+    `receiver_mobile`         varchar(20)     NOT NULL COMMENT '收件人手机',
+    `receiver_area_id`        int             NOT NULL COMMENT '收件人地区编号',
+    `receiver_post_code`      int                      DEFAULT NULL COMMENT '收件人邮编',
+    `receiver_detail_address` varchar(255)    NOT NULL COMMENT '收件人详细地址',
+    `refund_status`           int             NOT NULL DEFAULT '0' COMMENT '订单状态:[0:未退款 1:部分退款 2:全部退款]',
+    `refund_price`            int             NOT NULL DEFAULT '0' COMMENT '退款金额,单位:分',
+    `coupon_id`               bigint unsigned NOT NULL COMMENT '优惠劵编号',
+    `creator`                 varchar(64)              DEFAULT '' COMMENT '创建者',
+    `create_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`                 varchar(64)              DEFAULT '' COMMENT '更新者',
+    `update_time`             datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`                 bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='交易订单表';
+
+
+DROP TABLE IF EXISTS `trade_order_item`;
+CREATE TABLE `trade_order_item`
+(
+    `id`                    bigint          NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+    `user_id`               bigint unsigned NOT NULL COMMENT '用户编号',
+    `order_Id`              bigint unsigned NOT NULL COMMENT '订单编号',
+    `spu_id`                bigint unsigned NOT NULL COMMENT '商品 SPU 编号',
+    `sku_id`                bigint unsigned NOT NULL COMMENT '商品 SKU 编号',
+    `properties`            json                     DEFAULT NULL COMMENT '规格值数组,JSON 格式',
+    `name`                  varchar(128)    NOT NULL DEFAULT '' COMMENT '商品名称',
+    `pic_url`               varchar(200)             DEFAULT NULL COMMENT '商品图片',
+    `count`                 int             NOT NULL COMMENT '购买数量',
+    `commented`             bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否评论:[0:未评论 1:已评论]',
+    `original_price`        int             NOT NULL DEFAULT '0' COMMENT '商品原价(单),单位:分',
+    `total_original_price`  int             NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
+    `total_promotion_price` int             NOT NULL DEFAULT '0' COMMENT '商品级优惠(总),单位:分',
+    `present_price`         int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(单),单位:分。',
+    `total_present_price`   int             NOT NULL DEFAULT '0' COMMENT '最终购买金额(总),单位:分。',
+    `total_pay_price`       int             NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
+    `refund_status`         int             NOT NULL DEFAULT '0' COMMENT '退款状态:[0:未申请退款 1:申请退款 2:等待退款 3:退款成功]',
+    `refund_total`          int             NOT NULL DEFAULT '0' COMMENT '退款总金额,单位:分',
+    `creator`               varchar(64)              DEFAULT '' COMMENT '创建者',
+    `create_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`               varchar(64)              DEFAULT '' COMMENT '更新者',
+    `update_time`           datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`               bit(1)          NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='交易订单明细表';

+ 1 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java

@@ -15,6 +15,7 @@ import java.util.Arrays;
 @Getter
 public enum TerminalEnum implements IntArrayValuable {
 
+    //TODO terminal 重复,请参考 '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]'
     MINI_PROGRAM(1, "小程序"),
     H5(2, "H5"),
     IOS(3, "iOS"),

+ 30 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.api.sku;
+
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface ProductSkuApi {
+
+
+    /**
+     * 根据skuId列表 查询sku信息
+     *
+     * @param skuIds sku ID列表
+     * @return sku信息列表
+     */
+    List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds);
+
+    /**
+     * 批量扣减sku库存
+     *
+     * @param batchReqDTO sku库存信息列表
+     */
+    void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO);
+}

+ 45 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.api.sku.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SkuDecrementStockBatchReqDTO {
+
+
+    private List<Item> items;
+
+    @Data
+    public static class Item {
+
+        /**
+         * 商品 SPU 编号,自增
+         */
+        private Long productId;
+
+        /**
+         * 商品 SKU 编号,自增
+         */
+        private Long skuId;
+
+        /**
+         * 数量
+         */
+        private Integer count;
+
+    }
+
+    public static SkuDecrementStockBatchReqDTO of(List<Item> items) {
+        return new SkuDecrementStockBatchReqDTO(items);
+    }
+
+}

+ 93 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuInfoRespDTO.java

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.product.api.sku.dto;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+public class SkuInfoRespDTO {
+
+    /**
+     * 商品 SKU 编号,自增
+     */
+    private Long id;
+    /**
+     * 商品 SKU 名字
+     */
+    private String name;
+    /**
+     * SPU 编号
+     */
+    private Long spuId;
+
+    /**
+     * 规格值数组,JSON 格式
+     */
+    private List<Property> properties;
+    /**
+     * 销售价格,单位:分
+     */
+    private Integer price;
+    /**
+     * 市场价,单位:分
+     */
+    private Integer marketPrice;
+    /**
+     * 成本价,单位:分
+     */
+    private Integer costPrice;
+    /**
+     * SKU 的条形码
+     */
+    private String barCode;
+    /**
+     * 图片地址
+     */
+    private String picUrl;
+    /**
+     * SKU 状态
+     * <p>
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 库存
+     */
+    private Integer stock;
+    /**
+     * 预警预存
+     */
+    private Integer warnStock;
+    /**
+     * 商品重量,单位:kg 千克
+     */
+    private Double weight;
+    /**
+     * 商品体积,单位:m^3 平米
+     */
+    private Double volume;
+
+    /**
+     * 商品属性
+     */
+    @Data
+    public static class Property {
+
+        /**
+         * 属性编号
+         */
+        private Long propertyId;
+        /**
+         * 属性值编号
+         */
+        private Long valueId;
+
+    }
+
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.product.api.spu;
+
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface ProductSpuApi {
+
+
+    /**
+     * 根据spuId列表 查询spu信息
+     *
+     * @param spuIds spu ID列表
+     * @return spu信息列表
+     */
+    List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds);
+}

+ 124 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/SpuInfoRespDTO.java

@@ -0,0 +1,124 @@
+package cn.iocoder.yudao.module.product.api.spu.dto;
+
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+public class SpuInfoRespDTO {
+
+    /**
+     * 商品 SPU 编号,自增
+     */
+    private Long id;
+
+    // ========== 基本信息 =========
+
+    /**
+     * 商品名称
+     */
+    private String name;
+    /**
+     * 商品编码
+     */
+    private String code;
+    /**
+     * 商品卖点
+     */
+    private String sellPoint;
+    /**
+     * 商品详情
+     */
+    private String description;
+    /**
+     * 商品分类编号
+     */
+    private Long categoryId;
+    /**
+     * 商品品牌编号
+     */
+    private Long brandId;
+    /**
+     * 商品图片的数组
+     * <p>
+     * 1. 第一张图片将作为商品主图,支持同时上传多张图;
+     * 2. 建议使用尺寸 800x800 像素以上、大小不超过 1M 的正方形图片;
+     * 3. 至少 1 张,最多上传 10 张
+     */
+    private List<String> picUrls;
+    /**
+     * 商品视频
+     */
+    private String videoUrl;
+
+    /**
+     * 排序字段
+     */
+    private Integer sort;
+    /**
+     * 商品状态
+     * <p>
+     * 枚举 {@link ProductSpuStatusEnum}
+     */
+    private Integer status;
+
+    // ========== SKU 相关字段 =========
+
+    /**
+     * 规格类型
+     * <p>
+     * 枚举 {@link ProductSpuSpecTypeEnum}
+     */
+    private Integer specType;
+    /**
+     * 最小价格,单位使用:分
+     * <p>
+     * 基于其对应的 {@link SkuInfoRespDTO#getPrice()} 最小值
+     */
+    private Integer minPrice;
+    /**
+     * 最大价格,单位使用:分
+     * <p>
+     * 基于其对应的 {@link SkuInfoRespDTO#getPrice()} 最大值
+     */
+    private Integer maxPrice;
+    /**
+     * 市场价,单位使用:分
+     * <p>
+     * 基于其对应的 {@link SkuInfoRespDTO#getMarketPrice()} 最大值
+     */
+    private Integer marketPrice;
+    /**
+     * 总库存
+     * <p>
+     * 基于其对应的 {@link SkuInfoRespDTO#getStock()} 求和
+     */
+    private Integer totalStock;
+    /**
+     * 是否展示库存
+     */
+    private Boolean showStock;
+
+    // ========== 统计相关字段 =========
+
+    /**
+     * 商品销量
+     */
+    private Integer salesCount;
+    /**
+     * 虚拟销量
+     */
+    private Integer virtualSalesCount;
+    /**
+     * 商品点击量
+     */
+    private Integer clickCount;
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/enums/ErrorCodeConstants.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.enums.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Trade-Order 错误码 Core 枚举类
+ * <p>
+ * Trade-Order 系统,使用 1-011-000-000 段
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface ErrorCodeConstants {
+
+    /**
+     * ========== Order 模块 1-011-000-000 ==========
+     */
+    ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在");
+    ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖");
+    ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖");
+    ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足");
+
+}

+ 13 - 0
yudao-module-mall/yudao-module-trade-biz/pom.xml

@@ -23,12 +23,25 @@
             <artifactId>yudao-module-trade-api</artifactId>
             <version>${revision}</version>
         </dependency>
+
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-product-api</artifactId>
             <version>${revision}</version>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-pay-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-market-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 业务组件 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 18 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java

@@ -1,15 +1,19 @@
 package cn.iocoder.yudao.module.trade.controller.app.order;
 
+import cn.hutool.extra.servlet.ServletUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderGetCreateInfoRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderRespVO;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -19,12 +23,16 @@ import javax.servlet.http.HttpServletRequest;
 @Api(tags = "用户 App - 交易订单")
 @RestController
 @RequestMapping("/trade/order")
+@RequiredArgsConstructor
 @Validated
 @Slf4j
 public class AppTradeOrderController {
 
     // TODO 在思考下;
 
+    private final TradeOrderService tradeOrderService;
+
+
     @GetMapping("/get-create-info")
     @ApiOperation("基于商品,确认创建订单")
     @PreAuthenticated
@@ -36,11 +44,18 @@ public class AppTradeOrderController {
     @PostMapping("/create")
     @ApiOperation("创建订单")
     @PreAuthenticated
-    public CommonResult<Integer> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
-                                                  HttpServletRequest servletRequest) {
+    public CommonResult<Long> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
+                                               HttpServletRequest servletRequest) {
 //        return success(tradeOrderService.createTradeOrder(UserSecurityContextHolder.getUserId(),
 //                HttpUtil.getIp(servletRequest), createReqVO));
-        return null;
+        // 获取登录用户
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // 获取用户ip地址
+        String clientIp = ServletUtil.getClientIP(servletRequest);
+        // 创建交易订单,预支付记录
+        Long result = tradeOrderService.createTradeOrder(loginUserId, clientIp, createReqVO);
+
+        return CommonResult.success(result);
     }
 
     @GetMapping("/get")

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java

@@ -38,7 +38,7 @@ public class AppTradeOrderCreateReqVO {
 
         @ApiModelProperty(name = "商品 SKU 编号", required = true, example = "111")
         @NotNull(message = "商品 SKU 编号不能为空")
-        private Integer skuId;
+        private Long skuId;
 
         @ApiModelProperty(name = "商品 SKU 购买数量", required = true, example = "1024")
         @NotNull(message = "商品 SKU 购买数量不能为空")

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

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.trade.convert.order;
+
+import cn.iocoder.yudao.module.market.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 org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Mapper
+public interface TradeOrderConvert {
+
+    TradeOrderConvert INSTANCE = Mappers.getMapper(TradeOrderConvert.class);
+
+
+    TradeOrderDO convert(AppTradeOrderCreateReqVO createReqVO, PriceCalculateRespDTO.Order order);
+}

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

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.trade.convert.order;
+
+import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Mapper
+public interface TradeOrderItemConvert {
+
+    TradeOrderItemConvert INSTANCE = Mappers.getMapper(TradeOrderItemConvert.class);
+
+    /**
+     *
+     * @param tradeOrder 交易订单
+     * @param items sku列表价格
+     * @return 订单项
+     */
+    @Mappings({
+            @Mapping(source = "tradeOrder.userId", target = "userId"),
+            @Mapping(source = "tradeOrder.orderId", target = "orderId")
+    })
+    List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.Item> items);
+}

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

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.trade.convert.pay;
+
+import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface PayOrderConvert {
+
+    PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class);
+
+
+    PayOrderDataCreateReqDTO convert(TradeOrderDO tradeOrderDO);
+}

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

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.convert.price;
+
+import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
+import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Mapper
+public interface PriceConvert {
+
+    PriceConvert INSTANCE = Mappers.getMapper(PriceConvert.class);
+
+    @Mappings(
+            @Mapping(source = "userId" , target = "userId")
+    )
+    PriceCalculateReqDTO convert(AppTradeOrderCreateReqVO createReqVO , Long userId);
+}

+ 20 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/sku/ProductSkuConvert.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.trade.convert.sku;
+
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Mapper
+public interface ProductSkuConvert {
+
+    ProductSkuConvert INSTANCE = Mappers.getMapper(ProductSkuConvert.class);
+
+    List<SkuDecrementStockBatchReqDTO.Item> convert(List<TradeOrderItemDO> tradeOrderItems);
+}

+ 11 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java

@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.order;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
+}

+ 11 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java

@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.orderitem;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
+}

+ 19 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.trade.service.order;
+
+import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface TradeOrderService {
+
+    /**
+     * 创建交易订单
+     * @param loginUserId 登录用户
+     * @param clientIp 用户ip地址
+     * @param createReqVO 创建交易订单请求模型
+     * @return 交易订单创建结果
+     */
+    Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO);
+}

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

@@ -0,0 +1,138 @@
+package cn.iocoder.yudao.module.trade.service.order;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.market.api.price.PriceApi;
+import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
+import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
+import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
+import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO.Item;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderItemConvert;
+import cn.iocoder.yudao.module.trade.convert.pay.PayOrderConvert;
+import cn.iocoder.yudao.module.trade.convert.price.PriceConvert;
+import cn.iocoder.yudao.module.trade.convert.sku.ProductSkuConvert;
+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.mysql.order.TradeOrderMapper;
+import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
+import cn.iocoder.yudao.module.trade.enums.enums.ErrorCodeConstants;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Service
+@RequiredArgsConstructor
+public class TradeOrderServiceImpl implements TradeOrderService {
+
+    private final TradeOrderMapper tradeOrderMapper;
+
+    private final TradeOrderItemMapper tradeOrderItemMapper;
+
+    private final PriceApi priceApi;
+
+    private final ProductSkuApi productSkuApi;
+
+    private final ProductSpuApi productSpuApi;
+
+    private final PayOrderApi payOrderApi;
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
+
+        List<Item> items = createReqVO.getItems();
+        //  商品SKU检查 sku可售状态,库存
+        List<SkuInfoRespDTO> skuInfos = productSkuApi.getSkusByIds(CollectionUtils.convertSet(items, Item::getSkuId));
+        Map<Long, SkuInfoRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, SkuInfoRespDTO::getId);
+        checkSaleableAndStockFromSpu(skuInfoMap, items);
+
+        //  商品SPU检查 sku可售状态,库存
+        List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpusByIds(CollectionUtils.convertSet(skuInfos, SkuInfoRespDTO::getSpuId));
+        checkSaleableFromSpu(spuInfos);
+
+        // 价格计算
+        PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, loginUserId);
+        PriceCalculateRespDTO priceResp = priceApi.calculatePrice(priceCalculateReqDTO);
+
+        // 订单信息记录
+        TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(createReqVO, priceResp.getOrder());
+        tradeOrderMapper.insert(tradeOrderDO);
+
+        // 订单项信息记录
+        List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getItems());
+        //-填充订单项-SKU信息
+        fillItemsInfoFromSku(tradeOrderItems, skuInfoMap);
+        tradeOrderItemMapper.insertBatch(tradeOrderItems);
+
+        // 库存扣减
+        List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
+        productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
+        // 生成预支付
+
+        PayOrderDataCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
+        return payOrderApi.createPayOrder(payOrderCreateReqDTO);
+    }
+
+    private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems,
+                                      Map<Long, SkuInfoRespDTO> spuInfos) {
+        for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
+            // 填充SKU信息
+            SkuInfoRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId());
+            tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId());
+            tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl());
+            tradeOrderItem.setName(skuInfoRespDTO.getName());
+            // todo
+            List<TradeOrderItemDO.Property> property =
+                    BeanUtil.copyToList(skuInfoRespDTO.getProperties(), TradeOrderItemDO.Property.class);
+            tradeOrderItem.setProperties(property);
+        }
+    }
+
+    private void checkSaleableFromSpu(List<SpuInfoRespDTO> spuInfos) {
+        SpuInfoRespDTO spu = CollectionUtils.findFirst(spuInfos,
+                spuInfoDTO -> !Objects.equals(ProductSpuStatusEnum.ENABLE.getStyle(), spuInfoDTO.getStatus()));
+        if (Objects.isNull(spu)) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SPU_NOT_SALE);
+        }
+    }
+
+    private void checkSaleableAndStockFromSpu(Map<Long, SkuInfoRespDTO> skuInfoMap,
+                                              List<Item> items) {
+        // sku 不存在
+        if (items.size() != skuInfoMap.size()) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND);
+        }
+        for (Item item : items) {
+            SkuInfoRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId());
+            // sku禁用
+            if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) {
+                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE);
+            }
+            // sku库存不足
+            if (item.getCount() > skuInfoDTO.getStock()) {
+                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_STOCK_NOT_ENOUGH);
+            }
+        }
+
+    }
+}

+ 7 - 0
yudao-module-pay/yudao-module-pay-api/pom.xml

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

+ 20 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pay.api.order;
+
+import javax.validation.Valid;
+
+/**
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface PayOrderApi {
+
+
+    /**
+     * 创建支付单
+     *
+     * @param reqDTO 创建请求
+     * @return 支付单编号
+     */
+    Long createPayOrder(@Valid PayOrderDataCreateReqDTO reqDTO);
+
+}

+ 65 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderDataCreateReqDTO.java

@@ -0,0 +1,65 @@
+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.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 支付单创建 Request DTO
+ * @author LeeYan9
+ */
+@Data
+public class PayOrderDataCreateReqDTO implements Serializable {
+
+    /**
+     * 应用编号
+     */
+    @NotNull(message = "应用编号不能为空")
+    private Long appId;
+    /**
+     * 用户 IP
+     */
+    @NotEmpty(message = "用户 IP 不能为空")
+    private String userIp;
+
+    // ========== 商户相关字段 ==========
+
+    /**
+     * 商户订单编号
+     */
+    @NotEmpty(message = "商户订单编号不能为空")
+    private String merchantOrderId;
+    /**
+     * 商品标题
+     */
+    @NotEmpty(message = "商品标题不能为空")
+    @Length(max = 32, message = "商品标题不能超过 32")
+    private String subject;
+    /**
+     * 商品描述
+     */
+    @NotEmpty(message = "商品描述信息不能为空")
+    @Length(max = 128, message = "商品描述信息长度不能超过128")
+    private String body;
+
+    // ========== 订单相关字段 ==========
+
+    /**
+     * 支付金额,单位:分
+     */
+    @NotNull(message = "支付金额不能为空")
+    @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
+    private Integer amount;
+
+    /**
+     * 支付过期时间
+     */
+    @NotNull(message = "支付过期时间不能为空")
+    private Date expireTime;
+
+}