创建交易订单-单元测试

This commit is contained in:
ex_yang.li@ca-nio.com 2022-09-15 16:57:04 +08:00
parent 7bf20ec72e
commit 5889093a64
26 changed files with 566 additions and 41 deletions

View File

@ -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 '发货时间',

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.market.api.price;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
/**
* @author LeeYan9
* @since 2022-09-06
*/
@Service
@Validated
public class PriceApiImpl implements PriceApi {
@Override
public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
return null;
}
}

View File

@ -1,6 +1,5 @@
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;

View File

@ -0,0 +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.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.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;
/**
* @author LeeYan9
* @since 2022-09-06
*/
@Service
@Validated
public class ProductSkuApiImpl implements ProductSkuApi {
@Resource
private ProductSkuMapper productSkuMapper;
@Override
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());
}
}

View File

@ -0,0 +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;
/**
* @author LeeYan9
* @since 2022-09-06
*/
@Service
@Validated
public class ProductSpuApiImpl implements ProductSpuApi {
@Resource
private ProductSpuMapper productSpuMapper;
@Override
public List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds) {
if (CollectionUtils.isAnyEmpty(spuIds)) {
return Collections.emptyList();
}
List<ProductSpuDO> productSpuDOList = productSpuMapper.selectBatchIds(spuIds);
return ProductSpuConvert.INSTANCE.convertList2(productSpuDOList);
}
}

View File

@ -1,6 +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.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuExcelVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
@ -37,4 +38,7 @@ public interface ProductSkuConvert {
List<ProductSkuExcelVO> convertList02(List<ProductSkuDO> list);
List<SkuInfoRespDTO> convertList03(List<ProductSkuDO> list);
}

View File

@ -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);
}

View File

@ -3,8 +3,10 @@ package cn.iocoder.yudao.module.product.dal.mysql.sku;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.Collections;
@ -50,4 +52,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);
}
}
}

View File

@ -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 相关 -->

View File

@ -4,6 +4,8 @@ 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.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
@ -16,5 +18,11 @@ public interface TradeOrderConvert {
TradeOrderConvert INSTANCE = Mappers.getMapper(TradeOrderConvert.class);
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);
}

View File

@ -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,14 +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")
})
List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.Item> items);
List<TradeOrderItemDO> convertList(List<PriceCalculateRespDTO.Item> items);
}

View File

@ -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);
}
}

View File

@ -2,10 +2,12 @@ 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;
import org.apache.ibatis.annotations.Mapper;
/**
* @author LeeYan9
* @since 2022-08-26
*/
@Mapper
public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
}

View File

@ -2,10 +2,12 @@ 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;
import org.apache.ibatis.annotations.Mapper;
/**
* @author LeeYan9
* @since 2022-08-26
*/
@Mapper
public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
}

View File

@ -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 {
}

View File

@ -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;
}

View File

@ -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.SkuInfoRespDTO;
@ -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.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<SkuInfoRespDTO> skuInfos = productSkuApi.getSkusByIds(CollectionUtils.convertSet(items, Item::getSkuId));
Map<Long, SkuInfoRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, SkuInfoRespDTO::getId);
checkSaleableAndStockFromSpu(skuInfoMap, items);
// 商品SPU检查 sku可售状态,库存
// 商品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());
fillTradeOrderInfoFromReqInfo(tradeOrderDO,createReqVO,loginUserId, clientIp);
tradeOrderMapper.insert(tradeOrderDO);
// 订单项信息记录
List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getItems());
List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(priceResp.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, SkuInfoRespDTO> 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, SkuInfoRespDTO> spuInfos) {
for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
// 填充订单信息
tradeOrderItem.setOrderId(tradeOrderDO.getId());
tradeOrderItem.setUserId(tradeOrderDO.getUserId());
// 填充SKU信息
SkuInfoRespDTO 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);

View File

@ -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.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.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.getSpusByIds(Collections.singleton(1L))).thenReturn(Lists.newArrayList(spuInfoRespDTO));
// mock 商品SkU数据
SkuInfoRespDTO skuInfoRespDTO = randomPojo(SkuInfoRespDTO.class, skuInfo -> {
skuInfo.setId(1L);
skuInfo.setStatus(CommonStatusEnum.ENABLE.getStatus());
skuInfo.setStock(randomInteger());
skuInfo.setSpuId(1L);
});
when(productSkuApi.getSkusByIds(Collections.singleton(1L))).thenReturn(Lists.newArrayList(skuInfoRespDTO));
// mock 价格信息
PriceCalculateRespDTO calculateRespDTO = randomPojo(PriceCalculateRespDTO.class, priceCalculateRespDTO -> {
PriceCalculateRespDTO.Item item = priceCalculateRespDTO.getItems().get(0);
item.setSkuId(1L);
item.setCount(2);
priceCalculateRespDTO.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.getItems().get(0).getPresentPrice(), tradeOrderItemDO.getPresentPrice());
}
}

View File

@ -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

View File

@ -0,0 +1,4 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
</configuration>

View File

@ -0,0 +1,2 @@
DELETE FROM trade_order;
DELETE FROM trade_order_item;

View File

@ -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")
);

View File

@ -15,6 +15,6 @@ public interface PayOrderApi {
* @param reqDTO 创建请求
* @return 支付单编号
*/
Long createPayOrder(@Valid PayOrderDataCreateReqDTO reqDTO);
Long createPayOrder(@Valid PayOrderInfoCreateReqDTO reqDTO);
}

View File

@ -14,7 +14,7 @@ import java.util.Date;
* @author LeeYan9
*/
@Data
public class PayOrderDataCreateReqDTO implements Serializable {
public class PayOrderInfoCreateReqDTO implements Serializable {
/**
* 应用编号

View File

@ -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;
}
}

View File

@ -78,6 +78,10 @@ yudao:
timeout: 5m
width: 160
height: 60
trade:
order:
app-id: 1
merchant-order-id: 1
codegen:
base-package: ${yudao.info.base-package}
db-schemas: ${spring.datasource.dynamic.datasource.master.name}