commit
952e6aa4a8
|
@ -1,4 +1,5 @@
|
|||
/**todo cancelType 设置默认值 0?*/
|
||||
DROP TABLE IF EXISTS `trade_order`;
|
||||
CREATE TABLE `trade_order`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
|
||||
|
@ -10,9 +11,10 @@ CREATE TABLE `trade_order`
|
|||
`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:已通过货到付款交易]',
|
||||
`cancel_type` int DEFAULT NULL COMMENT '取消类型:[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
|
||||
`remark` varchar(200) DEFAULT NULL COMMENT '商家备注',
|
||||
`payed` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]',
|
||||
`pay_time` datetime DEFAULT NULL COMMENT '订单支付时间',
|
||||
`finish_time` datetime DEFAULT NULL COMMENT '订单完成时间',
|
||||
`cancel_time` datetime DEFAULT NULL COMMENT '订单取消时间',
|
||||
`sku_original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
|
||||
|
@ -20,11 +22,11 @@ CREATE TABLE `trade_order`
|
|||
`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 '配置模板的编号',
|
||||
`pay_order_id` int DEFAULT NULL COMMENT '支付订单编号',
|
||||
`pay_channel` int DEFAULT NULL COMMENT '支付成功的支付渠道',
|
||||
`delivery_type` int DEFAULT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
|
||||
`actual_delivery_type` int DEFAULT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
|
||||
`delivery_template_id` 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 '发货时间',
|
||||
|
@ -73,4 +75,4 @@ CREATE TABLE `trade_order_item`
|
|||
`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 ='交易订单明细表';
|
||||
) ENGINE = InnoDB COMMENT ='交易订单明细表';
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
package cn.iocoder.yudao.module.product.api.sku;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
|
||||
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
|
||||
import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品 SKU API 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @since 2022-08-26
|
||||
* @author LeeYan9
|
||||
* @since 2022-09-06
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class ProductSkuApiImpl implements ProductSkuApi {
|
||||
|
||||
@Resource
|
||||
private ProductSkuService productSkuService;
|
||||
private ProductSkuMapper productSkuMapper;
|
||||
|
||||
@Override
|
||||
public ProductSkuRespDTO getSku(Long id) {
|
||||
ProductSkuDO skuDO = productSkuService.getSku(id);
|
||||
return ProductSkuConvert.INSTANCE.convert02(skuDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
|
||||
List<ProductSkuDO> list = productSkuService.getSkuList(ids);
|
||||
return ProductSkuConvert.INSTANCE.convertList02(list);
|
||||
public List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds) {
|
||||
if (CollectionUtils.isAnyEmpty(skuIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ProductSkuDO> productSkuDOList = productSkuMapper.selectBatchIds(skuIds);
|
||||
return ProductSkuConvert.INSTANCE.convertList03(productSkuDOList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO) {
|
||||
|
||||
productSkuMapper.decrementStockBatch(batchReqDTO.getItems());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,23 +1,35 @@
|
|||
package cn.iocoder.yudao.module.product.api.spu;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
|
||||
import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品 SPU API 实现类
|
||||
*
|
||||
* @author LeeYan9
|
||||
* @since 2022-08-26
|
||||
* @since 2022-09-06
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class ProductSpuApiImpl implements ProductSpuApi {
|
||||
|
||||
@Resource
|
||||
private ProductSpuMapper productSpuMapper;
|
||||
|
||||
@Override
|
||||
public List<SpuInfoRespDTO> getSpuList(Collection<Long> spuIds) {
|
||||
return null;
|
||||
if (CollectionUtils.isAnyEmpty(spuIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ProductSpuDO> productSpuDOList = productSpuMapper.selectBatchIds(spuIds);
|
||||
return ProductSpuConvert.INSTANCE.convertList2(productSpuDOList);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.module.product.convert.sku;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
|
||||
|
@ -34,4 +36,7 @@ public interface ProductSkuConvert {
|
|||
|
||||
List<ProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> list);
|
||||
|
||||
List<SkuInfoRespDTO> convertList03(List<ProductSkuDO> list);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.module.product.convert.spu;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
|
||||
|
@ -34,4 +35,7 @@ public interface ProductSpuConvert {
|
|||
|
||||
AppSpuPageRespVO convertAppResp(ProductSpuDO list);
|
||||
|
||||
List<SpuInfoRespDTO> convertList2(List<ProductSpuDO> list);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.product.dal.mysql.sku;
|
|||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -31,4 +34,16 @@ public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
|
|||
delete(lambdaQueryWrapperX);
|
||||
}
|
||||
|
||||
default void decrementStockBatch(List<SkuDecrementStockBatchReqDTO.Item> items) {
|
||||
for (SkuDecrementStockBatchReqDTO.Item item : items) {
|
||||
// 扣减库存 cas 逻辑
|
||||
LambdaUpdateWrapper<ProductSkuDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
|
||||
.setSql(" stock = stock-" + item.getCount())
|
||||
.eq(ProductSkuDO::getSpuId, item.getProductId())
|
||||
.eq(ProductSkuDO::getId, item.getSkuId())
|
||||
.ge(ProductSkuDO::getStock, item.getCount());
|
||||
// 执行
|
||||
this.update(null, lambdaUpdateWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,16 @@
|
|||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
|
|
|
@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreate
|
|||
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.factory.Mappers;
|
||||
|
||||
/**
|
||||
|
@ -16,6 +17,12 @@ public interface TradeOrderConvert {
|
|||
|
||||
TradeOrderConvert INSTANCE = Mappers.getMapper(TradeOrderConvert.class);
|
||||
|
||||
@Mapping(source = "order.couponId", target = "couponId")
|
||||
TradeOrderDO convert(AppTradeOrderCreateReqVO createReqVO, PriceCalculateRespDTO.Order order);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "order.couponId", target = "couponId"),
|
||||
@Mapping(target = "remark", ignore = true),
|
||||
@Mapping(source = "createVO.remark", target = "userRemark"),
|
||||
@Mapping(source = "createVO.addressId", target = "receiverAreaId")
|
||||
})
|
||||
TradeOrderDO convert(AppTradeOrderCreateReqVO createVO, PriceCalculateRespDTO.Order order);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
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;
|
||||
|
@ -20,17 +17,8 @@ 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")
|
||||
})
|
||||
default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.OrderItem> items) {
|
||||
// TODO @Com: Mapstruct 生成会报错
|
||||
throw new UnsupportedOperationException("无法实现");
|
||||
}
|
||||
List<TradeOrderItemDO> convertList(List<PriceCalculateRespDTO.OrderItem> items);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
package cn.iocoder.yudao.module.trade.convert.pay;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author LeeYan9
|
||||
* @since 2022-08-26
|
||||
*/
|
||||
@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);
|
||||
|
||||
PayOrderDataCreateReqDTO convert(TradeOrderDO tradeOrderDO);
|
||||
@Named("convertCreateTimeToPayExpireTime")
|
||||
default Date convertCreateTimeToPayExpireTime(Date cancelTime) {
|
||||
return DateUtil.offsetMinute(new Date(), 30);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package cn.iocoder.yudao.module.trade.framework.order.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author LeeYan9
|
||||
* @since 2022-09-15
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(TradeOrderProperties.class)
|
||||
public class TradeOrderConfig {
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.yudao.module.trade.framework.order.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author LeeYan9
|
||||
* @since 2022-09-15
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "yudao.trade.order")
|
||||
@Data
|
||||
@Validated
|
||||
public class TradeOrderProperties {
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
@NotNull(message = "商户订单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
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.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
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.pay.api.order.PayOrderInfoCreateReqDTO;
|
||||
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.ProductSkuRespDTO;
|
||||
|
@ -27,10 +31,15 @@ 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 lombok.RequiredArgsConstructor;
|
||||
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;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -40,20 +49,31 @@ import java.util.Objects;
|
|||
* @since 2022-08-26
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TradeOrderServiceImpl implements TradeOrderService {
|
||||
|
||||
private final TradeOrderMapper tradeOrderMapper;
|
||||
@Resource
|
||||
private TradeOrderMapper tradeOrderMapper;
|
||||
|
||||
private final TradeOrderItemMapper tradeOrderItemMapper;
|
||||
@Resource
|
||||
private TradeOrderItemMapper tradeOrderItemMapper;
|
||||
|
||||
private final PriceApi priceApi;
|
||||
@Resource
|
||||
private PriceApi priceApi;
|
||||
|
||||
private final ProductSkuApi productSkuApi;
|
||||
@Resource
|
||||
private ProductSkuApi productSkuApi;
|
||||
|
||||
private final ProductSpuApi productSpuApi;
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
private final PayOrderApi payOrderApi;
|
||||
@Resource
|
||||
private PayOrderApi payOrderApi;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
|
||||
private static final String BLANK_PLACEHOLDER = " ";
|
||||
private static final String MULTIPLIER_PLACEHOLDER = "x";
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -61,46 +81,92 @@ public class TradeOrderServiceImpl implements TradeOrderService {
|
|||
public Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
|
||||
|
||||
List<Item> items = createReqVO.getItems();
|
||||
// 商品SKU检查 sku可售状态,库存
|
||||
// 商品SKU检查 sku可售状态,库存
|
||||
List<ProductSkuRespDTO> skuInfos = productSkuApi.getSkuList(CollectionUtils.convertSet(items, Item::getSkuId));
|
||||
Map<Long, ProductSkuRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, ProductSkuRespDTO::getId);
|
||||
checkSaleableAndStockFromSpu(skuInfoMap, items);
|
||||
|
||||
// 商品SPU检查 sku可售状态,库存
|
||||
// 商品SPU检查 sku可售状态,库存
|
||||
List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpuList(CollectionUtils.convertSet(skuInfos, ProductSkuRespDTO::getSpuId));
|
||||
checkSaleableFromSpu(spuInfos);
|
||||
|
||||
// 价格计算
|
||||
PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, loginUserId);
|
||||
PriceCalculateRespDTO priceResp = priceApi.calculatePrice(priceCalculateReqDTO);
|
||||
|
||||
// 订单信息记录
|
||||
TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(createReqVO, priceResp.getOrder());
|
||||
fillTradeOrderInfoFromReqInfo(tradeOrderDO,createReqVO,loginUserId, clientIp);
|
||||
tradeOrderMapper.insert(tradeOrderDO);
|
||||
|
||||
// 订单项信息记录
|
||||
List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getOrder().getItems());
|
||||
List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(priceResp.getOrder().getItems());
|
||||
//-填充订单项-SKU信息
|
||||
fillItemsInfoFromSku(tradeOrderItems, skuInfoMap);
|
||||
fillItemsInfoFromSkuAndOrder(tradeOrderDO, tradeOrderItems, skuInfoMap);
|
||||
tradeOrderItemMapper.insertBatch(tradeOrderItems);
|
||||
|
||||
// 库存扣减
|
||||
List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
|
||||
productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
|
||||
// 生成预支付
|
||||
|
||||
PayOrderDataCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
|
||||
// 构建预支付请求参数
|
||||
PayOrderInfoCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
|
||||
fillPayOrderInfoFromItems(payOrderCreateReqDTO, tradeOrderItems);
|
||||
// 生成预支付
|
||||
return payOrderApi.createPayOrder(payOrderCreateReqDTO);
|
||||
}
|
||||
|
||||
private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems,
|
||||
Map<Long, ProductSkuRespDTO> spuInfos) {
|
||||
private void fillTradeOrderInfoFromReqInfo(TradeOrderDO tradeOrderDO, AppTradeOrderCreateReqVO createReqVO,
|
||||
Long loginUserId, String clientIp) {
|
||||
tradeOrderDO.setUserId(loginUserId);
|
||||
tradeOrderDO.setUserIp(clientIp);
|
||||
tradeOrderDO.setSn(IdUtil.getSnowflakeNextId() + "");
|
||||
tradeOrderDO.setStatus(TradeOrderStatusEnum.WAITING_PAYMENT.getStatus());
|
||||
tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
|
||||
tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
||||
tradeOrderDO.setProductCount(CollectionUtils.getSumValue(createReqVO.getItems(), Item::getCount,Integer::sum));
|
||||
// todo 地址&用户信息解析
|
||||
|
||||
// todo 数据来源?
|
||||
tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal());
|
||||
}
|
||||
|
||||
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
|
||||
payOrderInfoCreateReqDTO.setSubject(StrUtils.maxLength(subject.subString(1), 32));
|
||||
payOrderInfoCreateReqDTO.setBody(StrUtils.maxLength(body.subString(1), 128));
|
||||
}
|
||||
|
||||
private void fillItemsInfoFromSkuAndOrder(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);
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package cn.iocoder.yudao.module.trade.service.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.market.api.price.PriceApi;
|
||||
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
|
||||
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.dal.mysql.order.TradeOrderMapper;
|
||||
import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomInteger;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author LeeYan9
|
||||
* @since 2022-09-07
|
||||
*/
|
||||
@Import({TradeOrderServiceImpl.class, TradeOrderConfig.class})
|
||||
class TradeOrderServiceTest extends BaseDbUnitTest {
|
||||
|
||||
|
||||
@Resource
|
||||
TradeOrderService tradeOrderService;
|
||||
@Resource
|
||||
TradeOrderMapper tradeOrderMapper;
|
||||
@Resource
|
||||
TradeOrderItemMapper tradeOrderItemMapper;
|
||||
@MockBean
|
||||
ProductSpuApi productSpuApi;
|
||||
@MockBean
|
||||
ProductSkuApi productSkuApi;
|
||||
@MockBean
|
||||
PriceApi priceApi;
|
||||
@MockBean
|
||||
private PayOrderApi payOrderApi;
|
||||
|
||||
@Test
|
||||
void testCreateTradeOrder_success() {
|
||||
// mock 商品SPU数据
|
||||
SpuInfoRespDTO spuInfoRespDTO = randomPojo(SpuInfoRespDTO.class, spuInfo -> {
|
||||
spuInfo.setId(1L);
|
||||
spuInfo.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
when(productSpuApi.getSpuList(Collections.singleton(1L))).thenReturn(Lists.newArrayList(spuInfoRespDTO));
|
||||
// mock 商品SkU数据
|
||||
ProductSkuRespDTO skuInfoRespDTO = randomPojo(ProductSkuRespDTO.class, skuInfo -> {
|
||||
skuInfo.setId(1L);
|
||||
skuInfo.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
skuInfo.setStock(randomInteger());
|
||||
skuInfo.setSpuId(1L);
|
||||
});
|
||||
when(productSkuApi.getSkuList(Collections.singleton(1L))).thenReturn(Lists.newArrayList(skuInfoRespDTO));
|
||||
// mock 价格信息
|
||||
PriceCalculateRespDTO calculateRespDTO = randomPojo(PriceCalculateRespDTO.class, priceCalculateRespDTO -> {
|
||||
PriceCalculateRespDTO.OrderItem item = priceCalculateRespDTO.getOrder().getItems().get(0);
|
||||
item.setSkuId(1L);
|
||||
item.setCount(2);
|
||||
priceCalculateRespDTO.getOrder().setItems(Collections.singletonList(item));
|
||||
});
|
||||
when(priceApi.calculatePrice(any())).thenReturn(calculateRespDTO);
|
||||
//mock 支付订单信息
|
||||
when(payOrderApi.createPayOrder(any())).thenReturn(1L);
|
||||
|
||||
// 准备请求数据
|
||||
AppTradeOrderCreateReqVO tradeOrderCreateReqVO = randomPojo(AppTradeOrderCreateReqVO.class, reqVO -> {
|
||||
AppTradeOrderCreateReqVO.Item item = randomPojo(AppTradeOrderCreateReqVO.Item.class, o -> {
|
||||
o.setSkuId(1L);
|
||||
o.setCount(2);
|
||||
});
|
||||
reqVO.setItems(Collections.singletonList(item));
|
||||
});
|
||||
// 创建交易订单,支付订单记录
|
||||
Long payOrderId = tradeOrderService.createTradeOrder(1L, "127.0.0.1", tradeOrderCreateReqVO);
|
||||
//断言交易订单
|
||||
TradeOrderDO tradeOrderDO = tradeOrderMapper.selectOne(TradeOrderDO::getUserId, 1L);
|
||||
assertNotNull(tradeOrderDO);
|
||||
//价格&用户
|
||||
assertEquals(calculateRespDTO.getOrder().getPayPrice(), tradeOrderDO.getPayPrice());
|
||||
assertEquals(1L, tradeOrderDO.getUserId());
|
||||
//断言交易订单项
|
||||
TradeOrderItemDO tradeOrderItemDO = tradeOrderItemMapper.selectOne(TradeOrderItemDO::getOrderId, tradeOrderDO.getId());
|
||||
assertNotNull(tradeOrderDO);
|
||||
//商品&用户
|
||||
assertEquals(skuInfoRespDTO.getId(), tradeOrderItemDO.getSkuId());
|
||||
assertEquals(1L, tradeOrderItemDO.getUserId());
|
||||
//价格
|
||||
assertEquals(calculateRespDTO.getOrder().getItems().get(0).getPresentPrice(), tradeOrderItemDO.getPresentPrice());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
spring:
|
||||
main:
|
||||
lazy-initialization: true # 开启懒加载,加快速度
|
||||
banner-mode: off # 单元测试,禁用 Banner
|
||||
|
||||
--- #################### 数据库相关配置 ####################
|
||||
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
druid:
|
||||
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
|
||||
initial-size: 1 # 单元测试,配置为 1,提升启动速度
|
||||
sql:
|
||||
init:
|
||||
schema-locations: classpath:/sql/create_tables.sql
|
||||
|
||||
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
|
||||
redis:
|
||||
host: 127.0.0.1 # 地址
|
||||
port: 16379 # 端口(单元测试,使用 16379 端口)
|
||||
database: 0 # 数据库索引
|
||||
|
||||
mybatis:
|
||||
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
||||
--- #################### 配置中心相关配置 ####################
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
# Lock4j 配置项(单元测试,禁用 Lock4j)
|
||||
|
||||
# Resilience4j 配置项
|
||||
|
||||
--- #################### 监控相关配置 ####################
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
# 芋道配置项,设置当前项目所有自定义的配置
|
||||
yudao:
|
||||
info:
|
||||
base-package: cn.iocoder.yudao.module
|
||||
trade:
|
||||
order:
|
||||
app-id: 1
|
||||
merchant-order-id: 1
|
|
@ -0,0 +1,4 @@
|
|||
<configuration>
|
||||
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
</configuration>
|
|
@ -0,0 +1,2 @@
|
|||
DELETE FROM trade_order;
|
||||
DELETE FROM trade_order_item;
|
|
@ -0,0 +1,78 @@
|
|||
/**todo cancelType 设置默认值 0?*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `trade_order`
|
||||
(
|
||||
`id` number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
`sn` varchar(32) NOT NULL,
|
||||
`type` int NOT NULL,
|
||||
`terminal` int NOT NULL,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`user_ip` varchar(30) NOT NULL,
|
||||
`user_remark` varchar(200),
|
||||
`status` int NOT NULL,
|
||||
`product_count` int NOT NULL,
|
||||
`cancel_type` int DEFAULT NULL,
|
||||
`remark` varchar(200),
|
||||
`payed` bit(1) NOT NULL DEFAULT FALSE,
|
||||
`pay_time` datetime DEFAULT NULL,
|
||||
`finish_time` datetime DEFAULT NULL,
|
||||
`cancel_time` datetime DEFAULT NULL,
|
||||
`sku_original_price` int NOT NULL DEFAULT '0',
|
||||
`sku_promotion_price` int NOT NULL DEFAULT '0',
|
||||
`order_promotion_price` int NOT NULL DEFAULT '0',
|
||||
`delivery_price` int NOT NULL DEFAULT '0',
|
||||
`pay_price` int DEFAULT '0',
|
||||
`pay_order_id` int DEFAULT NULL,
|
||||
`pay_channel` int DEFAULT NULL,
|
||||
`delivery_type` int NOT NULL DEFAULT '1',
|
||||
`actual_delivery_type` int NOT NULL DEFAULT '1',
|
||||
`delivery_template_id` int DEFAULT NULL,
|
||||
`express_no` int DEFAULT NULL,
|
||||
`delivery_status` bit(1) NOT NULL DEFAULT FALSE,
|
||||
`delivery_time` datetime DEFAULT NULL,
|
||||
`receive_time` datetime DEFAULT NULL,
|
||||
`receiver_name` varchar(20) DEFAULT NULL,
|
||||
`receiver_mobile` varchar(20) DEFAULT NULL,
|
||||
`receiver_area_id` int DEFAULT NULL,
|
||||
`receiver_post_code` int DEFAULT NULL,
|
||||
`receiver_detail_address` varchar(255) DEFAULT NULL,
|
||||
`refund_status` int NOT NULL DEFAULT '0',
|
||||
`refund_price` int NOT NULL DEFAULT '0',
|
||||
`coupon_id` bigint unsigned DEFAULT NULL,
|
||||
`creator` varchar(64) DEFAULT '',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updater` varchar(64) DEFAULT '',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` bit(1) NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `trade_order_item`
|
||||
(
|
||||
`id` number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`order_id` bigint unsigned NOT NULL,
|
||||
`spu_id` bigint unsigned NOT NULL,
|
||||
`sku_id` bigint unsigned NOT NULL,
|
||||
`properties` json DEFAULT NULL,
|
||||
`name` varchar(128) DEFAULT NULL,
|
||||
`pic_url` varchar(200) DEFAULT NULL,
|
||||
`count` int NOT NULL,
|
||||
`commented` bit(1) DEFAULT NULL,
|
||||
`original_price` int NOT NULL DEFAULT '0',
|
||||
`total_original_price` int NOT NULL DEFAULT '0',
|
||||
`total_promotion_price` int NOT NULL DEFAULT '0',
|
||||
`present_price` int NOT NULL DEFAULT '0',
|
||||
`total_present_price` int NOT NULL DEFAULT '0',
|
||||
`total_pay_price` int NOT NULL DEFAULT '0',
|
||||
`refund_status` int NOT NULL DEFAULT '0',
|
||||
`refund_total` int NOT NULL DEFAULT '0',
|
||||
`creator` varchar(64) DEFAULT '',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updater` varchar(64) DEFAULT '',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` bit(1) DEFAULT FALSE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
|
@ -15,6 +15,6 @@ public interface PayOrderApi {
|
|||
* @param reqDTO 创建请求
|
||||
* @return 支付单编号
|
||||
*/
|
||||
Long createPayOrder(@Valid PayOrderDataCreateReqDTO reqDTO);
|
||||
Long createPayOrder(@Valid PayOrderInfoCreateReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.Date;
|
|||
* @author LeeYan9
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderDataCreateReqDTO implements Serializable {
|
||||
public class PayOrderInfoCreateReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
|
@ -0,0 +1,19 @@
|
|||
package cn.iocoder.yudao.module.pay.api.order;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author LeeYan9
|
||||
* @since 2022-09-06
|
||||
*/
|
||||
@Service
|
||||
public class PayOrderApiImpl implements PayOrderApi {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createPayOrder(PayOrderInfoCreateReqDTO reqDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -95,6 +95,10 @@ yudao:
|
|||
base-package: ${yudao.info.base-package}
|
||||
captcha:
|
||||
enable: true # 验证码的开关,默认为 true;注意,优先读取数据库 infra_config 的 yudao.captcha.enable,所以请从数据库修改,可能需要重启项目
|
||||
trade:
|
||||
order:
|
||||
app-id: 1
|
||||
merchant-order-id: 1
|
||||
codegen:
|
||||
base-package: ${yudao.info.base-package}
|
||||
db-schemas: ${spring.datasource.dynamic.datasource.master.name}
|
||||
|
|
Loading…
Reference in New Issue