commit
61b0624a59
|
@ -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 ='交易订单明细表';
|
|
@ -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"),
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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, "商品库存不足");
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 购买数量不能为空")
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
Loading…
Reference in New Issue