mall + trade:获得订单结算信息,接入支付接口

This commit is contained in:
YunaiV 2023-06-03 00:46:56 +08:00
parent 5de8fa2e42
commit 2a9a869e01
21 changed files with 292 additions and 180 deletions

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.product.api.sku.dto;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import lombok.Data;
import java.util.List;
@ -25,7 +26,7 @@ public class ProductSkuRespDTO {
/**
* 属性数组
*/
private List<Property> properties;
private List<ProductPropertyValueDetailRespDTO> properties;
/**
* 销售价格单位
*/
@ -63,30 +64,4 @@ public class ProductSkuRespDTO {
*/
private Double volume;
/**
* 商品属性
*/
@Data
public static class Property {
/**
* 属性编号
*/
private Long propertyId;
/**
* 属性名字
*/
private String propertyName;
/**
* 属性值编号
*/
private Long valueId;
/**
* 属性值名字
*/
private String valueName;
}
}

View File

@ -18,6 +18,9 @@ public class AppProductSpuDetailRespVO {
@Schema(description = "商品名称", required = true, example = "芋道")
private String name;
@Schema(description = "商品简介", required = true, example = "我是一个快乐简介")
private String introduction;
@Schema(description = "商品详情", required = true, example = "我是商品描述")
private String description;

View File

@ -93,7 +93,6 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
assertNull(promotion.getId());
assertEquals(promotion.getName(), "会员折扣");
assertEquals(promotion.getType(), PromotionTypeEnum.MEMBER.getType());
assertEquals(promotion.getLevel(), PromotionLevelEnum.SKU.getLevel());
assertEquals(promotion.getTotalPrice(), 200);
assertEquals(promotion.getDiscountPrice(), 20);
assertTrue(promotion.getMatch());
@ -264,7 +263,6 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
assertEquals(promotion01.getId(), 1000L);
assertEquals(promotion01.getName(), "活动 1000 号");
assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
assertEquals(promotion01.getLevel(), PromotionLevelEnum.ORDER.getLevel());
assertEquals(promotion01.getTotalPrice(), 350);
assertEquals(promotion01.getDiscountPrice(), 70);
assertTrue(promotion01.getMatch());
@ -283,7 +281,6 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
assertEquals(promotion02.getId(), 2000L);
assertEquals(promotion02.getName(), "活动 2000 号");
assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
assertEquals(promotion02.getLevel(), PromotionLevelEnum.ORDER.getLevel());
assertEquals(promotion02.getTotalPrice(), 120);
assertEquals(promotion02.getDiscountPrice(), 60);
assertTrue(promotion02.getMatch());
@ -352,7 +349,6 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
assertEquals(promotion01.getId(), 1000L);
assertEquals(promotion01.getName(), "活动 1000 号");
assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
assertEquals(promotion01.getLevel(), PromotionLevelEnum.ORDER.getLevel());
assertEquals(promotion01.getTotalPrice(), 350);
assertEquals(promotion01.getDiscountPrice(), 0);
assertFalse(promotion01.getMatch());
@ -434,7 +430,6 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
assertEquals(promotion01.getId(), 1024L);
assertEquals(promotion01.getName(), "程序员节");
assertEquals(promotion01.getType(), PromotionTypeEnum.COUPON.getType());
assertEquals(promotion01.getLevel(), PromotionLevelEnum.COUPON.getLevel());
assertEquals(promotion01.getTotalPrice(), 350);
assertEquals(promotion01.getDiscountPrice(), 70);
assertTrue(promotion01.getMatch());

View File

@ -1,5 +1,10 @@
### /trade-order/settlement 获得订单结算信息
GET {{appApi}}/trade/order/settlement?cartIds=1
### /trade-order/settlement 获得订单结算信息(基于商品)
GET {{appApi}}/trade/order/settlement?type=0&items[0].skuId=1&items[0].count=2&items[1].skuId=2&items[1].count=3&couponId=1
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
### /trade-order/settlement 获得订单结算信息(基于购物车)
GET {{appApi}}/trade/order/settlement?type=0&items[0].cartId=50&couponId=1
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}

View File

@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
@ -25,7 +24,6 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -53,79 +51,9 @@ public class AppTradeOrderController {
@GetMapping("/settlement")
@Operation(summary = "获得订单结算信息")
@PreAuthenticated
public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(
@Valid AppTradeOrderSettlementReqVO settlementReqVO) {
if (true) {
public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(@Valid AppTradeOrderSettlementReqVO settlementReqVO) {
return success(tradeOrderService.settlementOrder(getLoginUserId(), settlementReqVO));
}
// return success(tradeOrderService.getOrderConfirmCreateInfo(UserSecurityContextHolder.getUserId(), skuId, quantity, couponCardId));
AppTradeOrderSettlementRespVO settlement = new AppTradeOrderSettlementRespVO();
AppTradeOrderSettlementRespVO.Price price = new AppTradeOrderSettlementRespVO.Price();
price.setTotalPrice(1000);
price.setDeliveryPrice(200);
price.setCouponPrice(100);
price.setPointPrice(50);
price.setPayPrice(950);
List<AppTradeOrderSettlementRespVO.Item> skus = new ArrayList<>();
AppTradeOrderSettlementRespVO.Item item1 = new AppTradeOrderSettlementRespVO.Item();
item1.setCartId(1L);
item1.setSpuId(2048L);
item1.setSpuName("Apple iPhone 12");
item1.setSkuId(1024);
item1.setPrice(500);
item1.setPicUrl("https://pro.crmeb.net/uploads/attach/2022/10/12/0c56f9abb80d2775fc1e80dbe4f8826a.jpg");
item1.setCount(2);
List<AppProductPropertyValueDetailRespVO> properties1 = new ArrayList<>();
AppProductPropertyValueDetailRespVO property1 = new AppProductPropertyValueDetailRespVO();
property1.setPropertyId(1L);
property1.setPropertyName("尺寸");
property1.setValueId(2L);
property1.setValueName("");
properties1.add(property1);
item1.setProperties(properties1);
AppTradeOrderSettlementRespVO.Item item2 = new AppTradeOrderSettlementRespVO.Item();
item2.setCartId(2L);
item2.setSpuId(3072L);
item2.setSpuName("Samsung Galaxy S21");
item2.setSkuId(2048);
item2.setPrice(800);
item2.setPicUrl("https://pro.crmeb.net/uploads/attach/2022/10/12/0c56f9abb80d2775fc1e80dbe4f8826a.jpg");
item2.setCount(1);
List<AppProductPropertyValueDetailRespVO> properties2 = new ArrayList<>();
AppProductPropertyValueDetailRespVO property2 = new AppProductPropertyValueDetailRespVO();
property2.setPropertyId(10L);
property2.setPropertyName("颜色");
property2.setValueId(20L);
property2.setValueName("白色");
properties2.add(property2);
item2.setProperties(properties2);
skus.add(item1);
skus.add(item2);
settlement.setItems(skus);
settlement.setPrice(price);
AppTradeOrderSettlementRespVO.Address address = new AppTradeOrderSettlementRespVO.Address();
address.setId(1L);
address.setName("John");
address.setMobile("18888888888");
address.setProvinceId(1L);
address.setProvinceName("Beijing");
address.setCityId(1L);
address.setCityName("Beijing");
address.setDistrictId(1L);
address.setDistrictName("Chaoyang Distripct");
address.setDetailAddress("No. 10, Xinzhong Street, Chaoyang District");
address.setDefaulted(true);
settlement.setAddress(address);
return success(settlement);
}
@PostMapping("/create")
@Operation(summary = "创建订单")

View File

@ -1,23 +1,60 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "用户 App - 交易订单结算 Request VO")
@Data
public class AppTradeOrderSettlementReqVO {
@NotNull(message = "交易类型不能为空")
@InEnum(value = TradeOrderTypeEnum.class, message = "交易类型必须是 {value}")
private Integer type;
@Schema(description = "商品项数组", required = true)
@NotNull(message = "商品不能为空")
private List<Item> items;
@Schema(description = "收件地址编号", example = "1")
private Long addressId;
@Schema(description = "优惠劵编号", example = "1024")
private Long couponId;
@Schema(description = "购物车项的编号数组", required = true, example = "true")
@NotEmpty(message = "购物车项不能为空")
private List<Long> cartIds;
@Data
@Schema(description = "用户 App - 商品项")
@Valid
public static class Item {
@Schema(description = "商品 SKU 编号", example = "2048")
private Long skuId;
@Schema(description = "购买数量", example = "1")
@Min(value = 1, message = "购买数量最小值为 {value}")
private Integer count;
@Schema(description = "购物车项的编号", example = "1024")
private Long cartId;
@AssertTrue(message = "商品不正确")
@JsonIgnore
public boolean isValid() {
// 组合一skuId + count 使用商品 SKU
if (skuId != null && count != null) {
return true;
}
// 组合二cartId 使用购物车项
return cartId != null;
}
}
}

View File

@ -91,17 +91,17 @@ public class AppTradeOrderSettlementRespVO {
private String mobile;
@Schema(description = "省份编号", required = true, example = "1")
private Long provinceId;
private Integer provinceId;
@Schema(description = "省份名字", required = true, example = "北京")
private String provinceName;
@Schema(description = "城市编号", required = true, example = "1")
private Long cityId;
private Integer cityId;
@Schema(description = "城市名字", required = true, example = "北京")
private String cityName;
@Schema(description = "地区编号", required = true, example = "1")
private Long districtId;
private Integer districtId;
@Schema(description = "地区名字", required = true, example = "朝阳区")
private String districtName;

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.convert.order;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
@ -18,14 +19,15 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.Prod
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@ -241,4 +243,45 @@ public interface TradeOrderConvert {
AppTradeOrderItemRespVO convert03(TradeOrderItemDO bean);
default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
List<TradeCartDO> cartList) {
TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO();
reqBO.setUserId(userId).setType(settlementReqVO.getType())
.setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId())
.setItems(new ArrayList<>(settlementReqVO.getItems().size()));
// 商品项的构建
Map<Long, TradeCartDO> cartMap = convertMap(cartList, TradeCartDO::getId);
for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {
// 情况一skuId + count
if (item.getSkuId() != null) {
reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(item.getSkuId()).setCount(item.getCount())
.setSelected(true)); // true 的原因下单一定选中
continue;
}
// 情况二cartId
TradeCartDO cart = cartMap.get(item.getCartId());
if (cart == null) {
continue;
}
reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(cart.getSkuId()).setCount(cart.getCount())
.setCartId(item.getCartId()).setSelected(true)); // true 的原因下单一定选中
}
return reqBO;
}
default AppTradeOrderSettlementRespVO convert(TradePriceCalculateRespBO calculate, AddressRespDTO address) {
AppTradeOrderSettlementRespVO respVO = convert0(calculate, address);
if (address != null) {
Area area = AreaUtils.getArea(address.getAreaId());
respVO.getAddress().setDistrictId(area.getId());
respVO.getAddress().setDistrictName(area.getName());
respVO.getAddress().setCityId(area.getParent().getId());
respVO.getAddress().setCityName(area.getParent().getName());
respVO.getAddress().setProvinceId(area.getParent().getParent().getId());
respVO.getAddress().setProvinceName(area.getParent().getParent().getName());
}
return respVO;
}
AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, AddressRespDTO address);
}

View File

@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Mapper
public interface TradeCartMapper extends BaseMapperX<TradeCartDO> {
@ -70,4 +71,10 @@ public interface TradeCartMapper extends BaseMapperX<TradeCartDO> {
update(updateObject, new LambdaQueryWrapper<TradeCartDO>().in(TradeCartDO::getId, ids));
}
default List<TradeCartDO> selectListByUserId(Long userId, Set<Long> ids) {
return selectList(new LambdaQueryWrapper<TradeCartDO>()
.eq(TradeCartDO::getUserId, userId)
.in(TradeCartDO::getId, ids));
}
}

View File

@ -4,10 +4,13 @@ import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 购物车 Service 接口
@ -67,6 +70,15 @@ public interface TradeCartService {
*/
AppTradeCartListRespVO getCartList(Long userId);
/**
* 查询用户的购物车列表
*
* @param userId 用户编号
* @param ids 购物项的编号
* @return 购物车列表
*/
List<TradeCartDO> getCartList(Long userId, Set<Long> ids);
/**
* 获得用户的购物车商品 SPU 数量的 Map
*

View File

@ -17,10 +17,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -166,6 +163,14 @@ public class TradeCartServiceImpl implements TradeCartService {
return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
}
@Override
public List<TradeCartDO> getCartList(Long userId, Set<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return cartMapper.selectListByUserId(userId, ids);
}
private void deleteCartIfSpuDeleted(List<TradeCartDO> carts, List<ProductSpuRespDTO> spus) {
// 如果 SPU 被删除则删除购物车对应的商品延迟删除
carts.removeIf(cart -> {

View File

@ -34,6 +34,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageRe
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
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.TradeOrderItemMapper;
@ -41,6 +42,10 @@ import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.trade.enums.order.*;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -50,8 +55,7 @@ import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_NOT_FOUND;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
@ -70,6 +74,11 @@ public class TradeOrderServiceImpl implements TradeOrderService {
@Resource
private TradeOrderItemMapper tradeOrderItemMapper;
@Resource
private TradeCartService tradeCartService;
@Resource
private TradePriceService tradePriceService;
@Resource
private PriceApi priceApi;
@Resource
@ -92,7 +101,48 @@ public class TradeOrderServiceImpl implements TradeOrderService {
@Override
public AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) {
return null;
// 1. 获得收货地址
AddressRespDTO address = getAddress(userId, settlementReqVO.getAddressId());
if (address != null) {
settlementReqVO.setAddressId(address.getId());
}
// 2. 计算价格
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO);
// 3. 拼接返回
return TradeOrderConvert.INSTANCE.convert(calculateRespBO, address);
}
/**
* 获得用户地址
*
* @param userId 用户编号
* @param addressId 地址编号
* @return 地址
*/
private AddressRespDTO getAddress(Long userId, Long addressId) {
if (addressId != null) {
return addressApi.getAddress(addressId, userId);
}
return addressApi.getDefaultAddress(userId);
}
/**
* 计算订单价格
*
* @param userId 用户编号
* @param settlementReqVO 结算信息
* @return 订单价格
*/
private TradePriceCalculateRespBO calculatePrice(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) {
// 1. 如果来自购物车则获得购物车的商品
List<TradeCartDO> cartList = tradeCartService.getCartList(userId,
convertSet(settlementReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId));
// 2. 计算价格
TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, settlementReqVO, cartList);
return tradePriceService.calculatePrice(calculateReqBO);
}
@Override
@ -120,29 +170,6 @@ public class TradeOrderServiceImpl implements TradeOrderService {
return tradeOrderDO.getId();
}
// /**
// * 校验商品 SKU 是否可出售
// *
// * @param items 商品 SKU
// * @return 商品 SKU 数组
// */
// private List<ProductSkuRespDTO> validateSkuSaleable(List<Item> items) {
// List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(items, Item::getSkuId));
// // SKU 不存在
// if (items.size() != skus.size()) {
// throw exception(ORDER_CREATE_SKU_NOT_FOUND);
// }
// // 校验库存不足
// Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
// items.forEach(item -> {
// ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
// if (item.getCount() > sku.getStock()) {
// throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH);
// }
// });
// return skus;
// }
/**
* 校验商品 SPU 是否可出售
*

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.price;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import javax.validation.Valid;
/**
* 价格计算 Service 接口
*
@ -16,6 +18,6 @@ public interface TradePriceService {
* @param calculateReqDTO 计算信息
* @return 计算结果
*/
TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqDTO);
TradePriceCalculateRespBO calculatePrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
}

View File

@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.trade.service.price;
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.ProductSpuRespDTO;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
@ -15,8 +19,8 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
/**
@ -25,22 +29,28 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.PRICE_C
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class TradePriceServiceImpl implements TradePriceService {
@Resource
private ProductSkuApi productSkuApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private List<TradePriceCalculator> priceCalculators;
@Override
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
// 1. 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkSkus(calculateReqBO);
// 1.1 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO);
// 1.2 获得商品 SPU 数组
List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
// 2.1 计算价格
TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper
.buildCalculateResp(calculateReqBO, skuList);
.buildCalculateResp(calculateReqBO, spuList, skuList);
priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO));
// 2.2 如果最终支付金额小于等于 0则抛出业务异常
if (calculateRespBO.getPrice().getPayPrice() <= 0) {
@ -51,7 +61,7 @@ public class TradePriceServiceImpl implements TradePriceService {
return calculateRespBO;
}
private List<ProductSkuRespDTO> checkSkus(TradePriceCalculateReqBO reqBO) {
private List<ProductSkuRespDTO> checkSkuList(TradePriceCalculateReqBO reqBO) {
// 获得商品 SKU 数组
Map<Long, Integer> skuIdCountMap = convertMap(reqBO.getItems(),
TradePriceCalculateReqBO.Item::getSkuId, TradePriceCalculateReqBO.Item::getCount);
@ -70,4 +80,20 @@ public class TradePriceServiceImpl implements TradePriceService {
return skus;
}
private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
// 获得商品 SPU 数组
List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
// 校验商品 SPU
spus.forEach(spu -> {
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
throw exception(SPU_NOT_ENABLE);
}
});
return spus;
}
}

View File

@ -21,7 +21,7 @@ public class TradePriceCalculateReqBO {
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer orderType;
private Integer type;
/**
* 用户编号

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import lombok.Data;
@ -23,7 +24,7 @@ public class TradePriceCalculateRespBO {
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer orderType;
private Integer type;
/**
* 订单价格
@ -163,7 +164,26 @@ public class TradePriceCalculateRespBO {
*/
private Integer payPrice;
// TODO 芋艿这里补充下基本信息简单一点
// ========== 商品信息 ==========
/**
* 商品名
*/
private String spuName;
/**
* 商品图片
*
* 优先级SKU.picUrl > SPU.picUrl
*/
private String picUrl;
/**
* 分类编号
*/
private Long categoryId;
/**
* 商品属性数组
*/
private List<ProductPropertyValueDetailRespDTO> properties;
}
@ -189,12 +209,6 @@ public class TradePriceCalculateRespBO {
* 枚举 {@link PromotionTypeEnum}
*/
private Integer type;
/**
* 营销级别
*
* 枚举 {@link PromotionLevelEnum}
*/
private Integer level;
/**
* 计算时的原价单位
*/

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@ -14,6 +15,8 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
import static java.util.Collections.singletonList;
// TODO 芋艿改成父类
/**
* {@link TradePriceCalculator} 的工具类
*
@ -24,25 +27,42 @@ import static java.util.Collections.singletonList;
public class TradePriceCalculatorHelper {
public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateReqBO param,
List<ProductSkuRespDTO> skuList) {
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
// 创建 PriceCalculateRespDTO 对象
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
result.setOrderType(param.getOrderType());
result.setType(param.getType());
result.setPromotions(new ArrayList<>());
// 创建它的 OrderItem 属性
Map<Long, TradePriceCalculateReqBO.Item> skuItemMap = convertMap(param.getItems(),
TradePriceCalculateReqBO.Item::getSkuId);
result.setItems(new ArrayList<>(skuItemMap.size()));
skuList.forEach(sku -> {
TradePriceCalculateReqBO.Item skuItem = skuItemMap.get(sku.getId());
TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem()
// SKU 字段
.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(skuItem.getCount()).setCartId(skuItem.getCartId()).setSelected(skuItem.getSelected())
// 价格字段
.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * skuItem.getCount())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0);
result.setItems(new ArrayList<>(param.getItems().size()));
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skuList, ProductSkuRespDTO::getId);
param.getItems().forEach(item -> {
ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
if (sku == null) {
return;
}
ProductSpuRespDTO spu = spuMap.get(sku.getSpuId());
if (spu == null) {
return;
}
// 商品项
TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem();
result.getItems().add(orderItem);
orderItem.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(item.getCount()).setCartId(item.getCartId()).setSelected(item.getSelected());
// sku 价格
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0);
// sku 信息
orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties());
// spu 信息
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId());
if (orderItem.getPicUrl() == null) {
orderItem.setPicUrl(spu.getPicUrl());
}
});
// 创建它的 Price 属性
result.setPrice(new TradePriceCalculateRespBO.Price());
recountAllPrice(result);

View File

@ -69,7 +69,7 @@ public class TradeAfterSaleServiceTest extends BaseDbUnitTest {
.setApplyPicUrls(asList("https://www.baidu.com/1.png", "https://www.baidu.com/2.png"));
// mock 方法交易订单项
TradeOrderItemDO orderItem = randomPojo(TradeOrderItemDO.class, o -> {
o.setOrderId(111L).setUserId(userId).setOrderDividePrice(200);
o.setOrderId(111L).setUserId(userId).setPayPrice(200);
o.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
});
when(tradeOrderService.getOrderItem(eq(1024L), eq(1L)))

View File

@ -18,4 +18,12 @@ public interface AddressApi {
*/
AddressRespDTO getAddress(Long id, Long userId);
/**
* 获得用户默认收件地址
*
* @param userId 用户编号
* @return 用户收件地址
*/
AddressRespDTO getDefaultAddress(Long userId);
}

View File

@ -29,7 +29,7 @@ public class AddressRespDTO {
/**
* 地区编号
*/
private Long areaId;
private Integer areaId;
/**
* 邮编
*/

View File

@ -25,4 +25,9 @@ public class AddressApiImpl implements AddressApi {
return AddressConvert.INSTANCE.convert02(addressService.getAddress(userId, id));
}
@Override
public AddressRespDTO getDefaultAddress(Long userId) {
return AddressConvert.INSTANCE.convert02(addressService.getDefaultUserAddress(userId));
}
}