list);
@Mappings({
@@ -151,9 +154,10 @@ public interface TradeOrderConvert {
TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog();
orderLog.setContent("订单操作" + i);
orderLog.setCreateTime(LocalDateTime.now());
+ orderLog.setUserType(i % 2 == 0 ? 2 : 1);
orderLogs.add(orderLog);
}
- orderVO.setOrderLog(orderLogs);
+ orderVO.setLogs(orderLogs);
return orderVO;
}
@@ -273,4 +277,10 @@ public interface TradeOrderConvert {
TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
+ default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
+ return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId()))
+ .setBasePrice(item.getPayPrice() * item.getCount())
+ .setFirstFixedPrice(sku.getSubCommissionFirstPrice())
+ .setSecondFixedPrice(sku.getSubCommissionSecondPrice());
+ }
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java
new file mode 100644
index 000000000..be69c6075
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 佣金记录 DO
+ *
+ * @author owen
+ */
+@TableName("trade_brokerage_record")
+@KeySequence("trade_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageRecordDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Integer id;
+ /**
+ * 用户编号
+ */
+ private Long userId;
+ /**
+ * 业务编号
+ */
+ private String bizId;
+ /**
+ * 业务类型
+ *
+ * 枚举 {@link BrokerageRecordBizTypeEnum}
+ */
+ private Integer bizType;
+
+ /**
+ * 标题
+ */
+ private String title;
+ /**
+ * 说明
+ */
+ private String description;
+
+ /**
+ * 金额
+ */
+ private Integer price;
+ /**
+ * 当前总佣金
+ */
+ private Integer totalPrice;
+
+ /**
+ * 状态
+ *
+ * 枚举 {@link BrokerageRecordStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 冻结时间(天)
+ */
+ private Integer frozenDays;
+ /**
+ * 解冻时间
+ */
+ private LocalDateTime unfreezeTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java
new file mode 100644
index 000000000..4348fa195
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 分销用户 DO
+ *
+ * @author owen
+ */
+@TableName("trade_brokerage_user")
+@KeySequence("trade_brokerage_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageUserDO extends BaseDO {
+
+ /**
+ * 用户编号
+ *
+ * 对应 MemberUserDO 的 id 字段
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 推广员编号
+ *
+ * 关联 MemberUserDO 的 id 字段
+ */
+ private Long bindUserId;
+ /**
+ * 推广员绑定时间
+ */
+ private LocalDateTime bindUserTime;
+
+ /**
+ * 是否有分销资格
+ */
+ private Boolean brokerageEnabled;
+ /**
+ * 成为分销员时间
+ */
+ private LocalDateTime brokerageTime;
+
+ /**
+ * 可用佣金
+ */
+ private Integer brokeragePrice;
+ /**
+ * 冻结佣金
+ */
+ private Integer frozenPrice;
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java
new file mode 100644
index 000000000..a0c6d3858
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 交易中心配置 DO
+ *
+ * @author owen
+ */
+@TableName(value = "trade_config", autoResultMap = true)
+@KeySequence("trade_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TradeConfigDO extends BaseDO {
+
+ /**
+ * 自增主键
+ */
+ @TableId
+ private Long id;
+
+ // ========== 分销相关 ==========
+
+ /**
+ * 是否启用分佣
+ */
+ private Boolean brokerageEnabled;
+ /**
+ * 分佣模式
+ *
+ * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
+ */
+ private Integer brokerageEnabledCondition;
+ /**
+ * 分销关系绑定模式
+ *
+ * 枚举 {@link BrokerageBindModeEnum 对应的类}
+ */
+ private Integer brokerageBindMode;
+ /**
+ * 分销海报图地址数组
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List brokeragePostUrls;
+ /**
+ * 一级返佣比例
+ */
+ private Integer brokerageFirstPercent;
+ /**
+ * 二级返佣比例
+ */
+ private Integer brokerageSecondPercent;
+ /**
+ * 用户提现最低金额
+ */
+ private Integer brokerageWithdrawMinPrice;
+ /**
+ * 提现银行
+ */
+ @TableField(typeHandler = IntegerListTypeHandler.class)
+ private List brokerageBankNames;
+ /**
+ * 佣金冻结时间(天)
+ */
+ private Integer brokerageFrozenDays;
+ /**
+ * 提现方式
+ *
+ * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
+ */
+ @TableField(typeHandler = IntegerListTypeHandler.class)
+ private List brokerageWithdrawType;
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java
new file mode 100644
index 000000000..b72b50ca7
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record;
+
+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.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 佣金记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageRecordMapper extends BaseMapperX {
+
+ default PageResult selectPage(BrokerageRecordPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(BrokerageRecordDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(BrokerageRecordDO::getBizType, reqVO.getBizType())
+ .eqIfPresent(BrokerageRecordDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(BrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(BrokerageRecordDO::getId));
+ }
+
+ default List selectListByStatusAndUnfreezeTimeLt(Integer status, LocalDateTime unfreezeTime) {
+ return selectList(new LambdaQueryWrapper()
+ .eq(BrokerageRecordDO::getStatus, status)
+ .lt(BrokerageRecordDO::getUnfreezeTime, unfreezeTime));
+ }
+
+ default int updateByIdAndStatus(Integer id, Integer status, BrokerageRecordDO updateObj) {
+ return update(updateObj, new LambdaQueryWrapper()
+ .eq(BrokerageRecordDO::getId, id)
+ .eq(BrokerageRecordDO::getStatus, status));
+ }
+
+ default BrokerageRecordDO selectByBizTypeAndBizId(Integer bizType, String bizId) {
+ return selectOne(BrokerageRecordDO::getBizType, bizType,
+ BrokerageRecordDO::getBizId, bizId);
+ }
+
+ // TODO @疯狂:mysql 关键字,大写哈;这样看起来清晰点;例如说 SELECT COUNT(1)
+ @Select("select count(1), sum(price) from trade_brokerage_record where user_id = #{userId} and biz_type = #{bizType} and status = #{status}")
+ UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
+ @Param("bizType") Integer bizType,
+ @Param("status") Integer status);
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java
new file mode 100644
index 000000000..7fa3e415a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java
@@ -0,0 +1,115 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
+
+import cn.hutool.core.lang.Assert;
+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.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 分销用户 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageUserMapper extends BaseMapperX {
+
+ default PageResult selectPage(BrokerageUserPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(BrokerageUserDO::getBindUserId, reqVO.getBindUserId())
+ .eqIfPresent(BrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
+ .betweenIfPresent(BrokerageUserDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(BrokerageUserDO::getId));
+ }
+
+ /**
+ * 更新用户可用佣金(增加)
+ *
+ * @param id 用户编号
+ * @param incrCount 增加佣金(正数)
+ */
+ default void updatePriceIncr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount > 0);
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" price = price + " + incrCount)
+ .eq(BrokerageUserDO::getId, id);
+ update(null, lambdaUpdateWrapper);
+ }
+
+ /**
+ * 更新用户可用佣金(减少)
+ * 注意:理论上佣金可能已经提现,这时会扣出负数,确保平台不会造成损失
+ *
+ * @param id 用户编号
+ * @param incrCount 增加佣金(负数)
+ */
+ default void updatePriceDecr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount < 0);
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" price = price + " + incrCount) // 负数,所以使用 + 号
+ .eq(BrokerageUserDO::getId, id);
+ update(null, lambdaUpdateWrapper);
+ }
+
+ /**
+ * 更新用户冻结佣金(增加)
+ *
+ * @param id 用户编号
+ * @param incrCount 增加冻结佣金(正数)
+ */
+ default void updateFrozenPriceIncr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount > 0);
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" frozen_price = frozen_price + " + incrCount)
+ .eq(BrokerageUserDO::getId, id);
+ update(null, lambdaUpdateWrapper);
+ }
+
+ /**
+ * 更新用户冻结佣金(减少)
+ * 注意:理论上冻结佣金可能已经解冻,这时会扣出负数,确保平台不会造成损失
+ *
+ * @param id 用户编号
+ * @param incrCount 减少冻结佣金(负数)
+ */
+ default void updateFrozenPriceDecr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount < 0);
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" frozen_price = frozen_price + " + incrCount) // 负数,所以使用 + 号
+ .eq(BrokerageUserDO::getId, id);
+ update(null, lambdaUpdateWrapper);
+ }
+
+ /**
+ * 更新用户冻结佣金(减少), 更新用户佣金(增加)
+ *
+ * @param id 用户编号
+ * @param incrCount 减少冻结佣金(负数)
+ * @return 更新条数
+ */
+ default int updateFrozenPriceDecrAndPriceIncr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount < 0);
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" frozen_price = frozen_price + " + incrCount + // 负数,所以使用 + 号
+ ", price = price + " + -incrCount) // 负数,所以使用 - 号
+ .eq(BrokerageUserDO::getId, id)
+ .ge(BrokerageUserDO::getFrozenPrice, -incrCount); // cas 逻辑
+ return update(null, lambdaUpdateWrapper);
+ }
+
+ default void updateBindUserIdAndBindUserTimeToNull(Long id) {
+ update(null, new LambdaUpdateWrapper()
+ .eq(BrokerageUserDO::getId, id)
+ .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null));
+ }
+
+ default void updateEnabledFalseAndBrokerageTimeToNull(Long id) {
+ update(null, new LambdaUpdateWrapper()
+ .eq(BrokerageUserDO::getId, id)
+ .set(BrokerageUserDO::getBrokerageEnabled, false).set(BrokerageUserDO::getBrokerageTime, null));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java
new file mode 100644
index 000000000..18a3f4df7
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 交易中心配置 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeConfigMapper extends BaseMapperX {
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/no/TradeOrderNoRedisDAO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/no/TradeOrderNoRedisDAO.java
new file mode 100644
index 000000000..8ad619269
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/no/TradeOrderNoRedisDAO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.trade.dal.redis.no;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+/**
+ * 订单序号的 Redis DAO
+ *
+ * @author HUIHUI
+ */
+@Repository
+public class TradeOrderNoRedisDAO {
+
+ public static final String TRADE_ORDER_NO_PREFIX = "O";
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ /**
+ * 生成序号
+ *
+ * @param prefix 前缀
+ * @return 序号
+ */
+ public String generate(String prefix) {
+ String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
+ Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
+ return noPrefix + no;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/BrokerageRecordUnfreezeJob.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/BrokerageRecordUnfreezeJob.java
new file mode 100644
index 000000000..c221408e5
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/BrokerageRecordUnfreezeJob.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.trade.job.brokerage;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 佣金解冻 Job
+ *
+ * @author owen
+ */
+@Component
+@TenantJob
+public class BrokerageRecordUnfreezeJob implements JobHandler {
+
+ @Resource
+ private BrokerageRecordService brokerageRecordService;
+
+ @Override
+ public String execute(String param) {
+ int count = brokerageRecordService.unfreezeRecord();
+ return StrUtil.format("解冻佣金 {} 个", count);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java
new file mode 100644
index 000000000..129413067
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位文件,无特殊用途
+ */
+package cn.iocoder.yudao.module.trade.job;
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
index eae983799..f54563d42 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
@@ -90,12 +90,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
@Override
public TradeAfterSaleDO getAfterSale(Long id) {
- TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
- // TODO @puhui999;读不到,不要这里报错哈;交给前端报错;一般是读取信息不到,message 提示,然后 close tab;
- if (afterSale == null) {
- throw exception(AFTER_SALE_NOT_FOUND);
- }
- return afterSale;
+ return tradeAfterSaleMapper.selectById(id);
}
// TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑?
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/BrokerageAddReqBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/BrokerageAddReqBO.java
new file mode 100644
index 000000000..ff16ba16a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/BrokerageAddReqBO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.bo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 佣金 增加 Request BO
+ *
+ * @author owen
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageAddReqBO {
+
+ /**
+ * 业务编号
+ */
+ @NotBlank(message = "业务编号不能为空")
+ private String bizId;
+ /**
+ * 佣金基数
+ */
+ @NotNull(message = "佣金基数不能为空")
+ private Integer basePrice;
+ /**
+ * 一级佣金(固定)
+ */
+ private Integer firstFixedPrice;
+ /**
+ * 二级佣金(固定)
+ */
+ private Integer secondFixedPrice;
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
new file mode 100644
index 000000000..4504290be
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.bo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 用户佣金合计 BO
+ *
+ * @author owen
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserBrokerageSummaryBO {
+
+ /**
+ * 佣金数量
+ */
+ private Integer count;
+ /**
+ * 佣金总额
+ */
+ private Integer price;
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordService.java
new file mode 100644
index 000000000..a6ef0b659
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordService.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 佣金记录 Service 接口
+ *
+ * @author owen
+ */
+public interface BrokerageRecordService {
+
+ /**
+ * 获得佣金记录
+ *
+ * @param id 编号
+ * @return 佣金记录
+ */
+ BrokerageRecordDO getBrokerageRecord(Integer id);
+
+ /**
+ * 获得佣金记录分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 佣金记录分页
+ */
+ PageResult getBrokerageRecordPage(BrokerageRecordPageReqVO pageReqVO);
+
+ /**
+ * 增加佣金
+ *
+ * @param userId 会员编号
+ * @param bizType 业务类型
+ * @param list 请求参数列表
+ */
+ void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, @Valid List list);
+
+ /**
+ * 取消佣金:将佣金记录,状态修改为已失效
+ *
+ * @param userId 会员编号
+ * @param bizType 业务类型
+ * @param bizId 业务编号
+ */
+ void cancelBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId);
+
+ /**
+ * 解冻佣金:将待结算的佣金记录,状态修改为已结算
+ *
+ * @return 解冻佣金的数量
+ */
+ int unfreezeRecord();
+
+ /**
+ * 汇总用户佣金
+ *
+ * @param userId 用户编号
+ * @param bizType 业务类型
+ * @param status 佣金状态
+ * @return 用户佣金汇总
+ */
+ UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status);
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImpl.java
new file mode 100644
index 000000000..7740d7d06
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImpl.java
@@ -0,0 +1,236 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.record;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.convert.brokerage.record.BrokerageRecordConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.BrokerageRecordMapper;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * 佣金记录 Service 实现类
+ *
+ * @author owen
+ */
+@Slf4j
+@Service
+@Validated
+public class BrokerageRecordServiceImpl implements BrokerageRecordService {
+
+ @Resource
+ private BrokerageRecordMapper brokerageRecordMapper;
+ @Resource
+ private TradeConfigService tradeConfigService;
+ @Resource
+ private BrokerageUserService brokerageUserService;
+
+ @Override
+ public BrokerageRecordDO getBrokerageRecord(Integer id) {
+ return brokerageRecordMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getBrokerageRecordPage(BrokerageRecordPageReqVO pageReqVO) {
+ return brokerageRecordMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, List list) {
+ TradeConfigDO memberConfig = tradeConfigService.getTradeConfig();
+ // 0 未启用分销功能
+ if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
+ log.warn("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,userId({})", userId);
+ return;
+ }
+
+ // 1.1 获得一级推广人
+ BrokerageUserDO firstUser = brokerageUserService.getBindBrokerageUser(userId);
+ if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
+ return;
+ }
+ // 1.2 计算一级分佣
+ addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(), BrokerageAddReqBO::getFirstFixedPrice, bizType);
+
+ // 2.1 获得二级推广员
+ if (firstUser.getBindUserId() == null) {
+ return;
+ }
+ BrokerageUserDO secondUser = brokerageUserService.getBrokerageUser(firstUser.getBindUserId());
+ if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
+ return;
+ }
+ // 2.2 计算二级分佣
+ addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(), BrokerageAddReqBO::getSecondFixedPrice, bizType);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void cancelBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId) {
+ // TODO @疯狂:userId 加进去查询,会不会更好一点?万一穿错参数;
+ BrokerageRecordDO record = brokerageRecordMapper.selectByBizTypeAndBizId(bizType.getType(), bizId);
+ if (record == null || ObjectUtil.notEqual(record.getUserId(), userId)) {
+ log.error("[cancelBrokerage][userId({})][bizId({}) 更新为已失效失败:记录不存在]", userId, bizId);
+ return;
+ }
+
+ // 1. 更新佣金记录为已失效
+ BrokerageRecordDO updateObj = new BrokerageRecordDO().setStatus(BrokerageRecordStatusEnum.CANCEL.getStatus());
+ int updateRows = brokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
+ if (updateRows == 0) {
+ log.error("[cancelBrokerage][record({}) 更新为已失效失败]", record.getId());
+ return;
+ }
+
+ // 2. 更新用户的佣金
+ if (BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus().equals(record.getStatus())) {
+ brokerageUserService.updateUserFrozenPrice(userId, -record.getPrice());
+ } else if (BrokerageRecordStatusEnum.SETTLEMENT.getStatus().equals(record.getStatus())) {
+ brokerageUserService.updateUserPrice(userId, -record.getPrice());
+ }
+ }
+
+ /**
+ * 计算佣金
+ *
+ * @param basePrice 佣金基数
+ * @param percent 佣金比例
+ * @param fixedPrice 固定佣金
+ * @return 佣金
+ */
+ int calculatePrice(Integer basePrice, Integer percent, Integer fixedPrice) {
+ // 1. 优先使用固定佣金
+ if (fixedPrice != null && fixedPrice > 0) {
+ return ObjectUtil.defaultIfNull(fixedPrice, 0);
+ }
+ // 2. 根据比例计算佣金
+ if (basePrice != null && basePrice > 0 && percent != null && percent > 0) {
+ return MoneyUtils.calculateRatePriceFloor(basePrice, Double.valueOf(percent));
+ }
+ return 0;
+ }
+
+ /**
+ * 增加用户佣金
+ *
+ * @param user 用户
+ * @param list 佣金增加参数列表
+ * @param brokerageFrozenDays 冻结天数
+ * @param brokeragePercent 佣金比例
+ * @param fixedPriceFun 固定佣金 // TODO 疯狂:这里是不是可以直接传递 fixedPrice 呀?
+ * @param bizType 业务类型
+ */
+ private void addBrokerage(BrokerageUserDO user, List list, Integer brokerageFrozenDays,
+ Integer brokeragePercent, Function fixedPriceFun,
+ BrokerageRecordBizTypeEnum bizType) {
+ // 1.1 处理冻结时间
+ LocalDateTime unfreezeTime = null;
+ if (brokerageFrozenDays != null && brokerageFrozenDays > 0) {
+ unfreezeTime = LocalDateTime.now().plusDays(brokerageFrozenDays);
+ }
+ // 1.2 计算分佣
+ int totalBrokerage = 0;
+ List records = new ArrayList<>();
+ for (BrokerageAddReqBO item : list) {
+ int brokeragePerItem = calculatePrice(item.getBasePrice(), brokeragePercent, fixedPriceFun.apply(item));
+ if (brokeragePerItem <= 0) {
+ continue;
+ }
+ records.add(BrokerageRecordConvert.INSTANCE.convert(user, bizType, item.getBizId(),
+ brokerageFrozenDays, brokeragePerItem, unfreezeTime, bizType.getTitle()));
+ totalBrokerage += brokeragePerItem;
+ }
+ if (CollUtil.isEmpty(records)) {
+ return;
+ }
+ // 1.3 保存佣金记录
+ brokerageRecordMapper.insertBatch(records);
+
+ // 2. 更新用户佣金
+ if (brokerageFrozenDays != null && brokerageFrozenDays > 0) { // 更新用户冻结佣金
+ brokerageUserService.updateUserFrozenPrice(user.getId(), totalBrokerage);
+ } else { // 更新用户可用佣金
+ brokerageUserService.updateUserPrice(user.getId(), totalBrokerage);
+ }
+ }
+
+ @Override
+ public int unfreezeRecord() {
+ // 1. 查询待结算的佣金记录
+ List records = brokerageRecordMapper.selectListByStatusAndUnfreezeTimeLt(
+ BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus(), LocalDateTime.now());
+ if (CollUtil.isEmpty(records)) {
+ return 0;
+ }
+
+ // 2. 遍历执行
+ int count = 0;
+ for (BrokerageRecordDO record : records) {
+ try {
+ boolean success = getSelf().unfreezeRecord(record);
+ if (success) {
+ count++;
+ }
+ } catch (Exception e) {
+ log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId(), e);
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status) {
+ UserBrokerageSummaryBO summaryBO = brokerageRecordMapper.selectCountAndSumPriceByUserIdAndBizTypeAndStatus(userId, bizType, status);
+ return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0);
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public boolean unfreezeRecord(BrokerageRecordDO record) {
+ // 更新记录状态
+ BrokerageRecordDO updateObj = new BrokerageRecordDO()
+ .setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus())
+ .setUnfreezeTime(LocalDateTime.now());
+ int updateRows = brokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
+ if (updateRows == 0) {
+ log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId());
+ return false;
+ }
+
+ // 更新用户冻结佣金
+ brokerageUserService.updateFrozenPriceDecrAndPriceIncr(record.getUserId(), -record.getPrice());
+ log.info("[unfreezeRecord][record({}) 更新为已结算成功]", record.getId());
+ return true;
+ }
+
+ /**
+ * 获得自身的代理对象,解决 AOP 生效问题
+ *
+ * @return 自己
+ */
+ private BrokerageRecordServiceImpl getSelf() {
+ return SpringUtil.getBean(getClass());
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java
new file mode 100644
index 000000000..de9d0a2b7
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java
@@ -0,0 +1,108 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 分销用户 Service 接口
+ *
+ * @author owen
+ */
+public interface BrokerageUserService {
+
+ /**
+ * 获得分销用户
+ *
+ * @param id 编号
+ * @return 分销用户
+ */
+ BrokerageUserDO getBrokerageUser(Long id);
+
+ /**
+ * 获得分销用户列表
+ *
+ * @param ids 编号
+ * @return 分销用户列表
+ */
+ List getBrokerageUserList(Collection ids);
+
+ /**
+ * 获得分销用户分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 分销用户分页
+ */
+ PageResult getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO);
+
+ /**
+ * 修改推广员编号
+ *
+ * @param id 用户编号
+ * @param bindUserId 推广员编号
+ */
+ void updateBrokerageUserId(Long id, Long bindUserId);
+
+ /**
+ * 修改推广资格
+ *
+ * @param id 用户编号
+ * @param enabled 推广资格
+ */
+ void updateBrokerageUserEnabled(Long id, Boolean enabled);
+
+ /**
+ * 获得用户的推广人
+ *
+ * @param id 用户编号
+ * @return 用户的推广人
+ */
+ BrokerageUserDO getBindBrokerageUser(Long id);
+
+ /**
+ * 更新用户佣金
+ *
+ * @param id 用户编号
+ * @param price 用户可用佣金
+ */
+ void updateUserPrice(Long id, Integer price);
+
+ /**
+ * 更新用户冻结佣金
+ *
+ * @param id 用户编号
+ * @param frozenPrice 用户冻结佣金
+ */
+ void updateUserFrozenPrice(Long id, Integer frozenPrice);
+
+ /**
+ * 更新用户冻结佣金(减少),更新用户佣金(增加)
+ *
+ * @param id 用户编号
+ * @param frozenPrice 减少冻结佣金(负数)
+ */
+ void updateFrozenPriceDecrAndPriceIncr(Long id, Integer frozenPrice);
+
+ // TODO @疯狂:这个后面可能要支持下,二级
+ /**
+ * 获得推广用户数量(一级)
+ *
+ * @param bindUserId 绑定的推广员编号
+ * @return 推广用户数量
+ */
+ Long getBrokerageUserCountByBindUserId(Long bindUserId);
+
+ /**
+ * 【会员】绑定推广员
+ *
+ * @param userId 用户编号
+ * @param bindUserId 推广员编号
+ * @param isNewUser 是否为新用户
+ * @return 是否绑定
+ */
+ boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser);
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java
new file mode 100644
index 000000000..1a168d966
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java
@@ -0,0 +1,222 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.user;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.BooleanUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.BrokerageUserMapper;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
+
+/**
+ * 分销用户 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class BrokerageUserServiceImpl implements BrokerageUserService {
+
+ @Resource
+ private BrokerageUserMapper brokerageUserMapper;
+
+ @Resource
+ private TradeConfigService tradeConfigService;
+
+ @Override
+ public BrokerageUserDO getBrokerageUser(Long id) {
+ return brokerageUserMapper.selectById(id);
+ }
+
+ @Override
+ public List getBrokerageUserList(Collection ids) {
+ return brokerageUserMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO) {
+ return brokerageUserMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public void updateBrokerageUserId(Long id, Long bindUserId) {
+ // 校验存在
+ validateBrokerageUserExists(id);
+
+ // 情况一:清除推广员
+ if (bindUserId == null) {
+ // 清除推广员
+ brokerageUserMapper.updateBindUserIdAndBindUserTimeToNull(id);
+ return;
+ }
+
+ // 情况二:修改推广员
+ // TODO @疯狂:要复用一些 validateCanBindUser 的校验哈;
+ brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
+ .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
+ }
+
+ @Override
+ public void updateBrokerageUserEnabled(Long id, Boolean enabled) {
+ // 校验存在
+ validateBrokerageUserExists(id);
+ if (BooleanUtil.isTrue(enabled)) {
+ // 开通推广资格
+ brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
+ .setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now()));
+ } else {
+ // 取消推广资格
+ brokerageUserMapper.updateEnabledFalseAndBrokerageTimeToNull(id);
+ }
+ }
+
+ private void validateBrokerageUserExists(Long id) {
+ if (brokerageUserMapper.selectById(id) == null) {
+ throw exception(BROKERAGE_USER_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public BrokerageUserDO getBindBrokerageUser(Long id) {
+ return Optional.ofNullable(id)
+ .map(this::getBrokerageUser)
+ .map(BrokerageUserDO::getBindUserId)
+ .map(this::getBrokerageUser)
+ .orElse(null);
+ }
+
+ @Override
+ public void updateUserPrice(Long id, Integer price) {
+ if (price > 0) {
+ brokerageUserMapper.updatePriceIncr(id, price);
+ } else if (price < 0) {
+ brokerageUserMapper.updatePriceDecr(id, price);
+ }
+ }
+
+ @Override
+ public void updateUserFrozenPrice(Long id, Integer frozenPrice) {
+ if (frozenPrice > 0) {
+ brokerageUserMapper.updateFrozenPriceIncr(id, frozenPrice);
+ } else if (frozenPrice < 0) {
+ brokerageUserMapper.updateFrozenPriceDecr(id, frozenPrice);
+ }
+ }
+
+ @Override
+ public void updateFrozenPriceDecrAndPriceIncr(Long id, Integer frozenPrice) {
+ Assert.isTrue(frozenPrice < 0);
+ int updateRows = brokerageUserMapper.updateFrozenPriceDecrAndPriceIncr(id, frozenPrice);
+ if (updateRows == 0) {
+ throw exception(BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH);
+ }
+ }
+
+ @Override
+ public Long getBrokerageUserCountByBindUserId(Long bindUserId) {
+ // TODO @疯狂:mapper 封装下哈;不直接在 service 调用这种基础 mapper 的基础方法
+ return brokerageUserMapper.selectCount(BrokerageUserDO::getBindUserId, bindUserId);
+ }
+
+ // TODO @疯狂:因为现在 user 会存在使用验证码直接注册,所以 isNewUser 不太好传递;我们是不是可以约定绑定的时间,createTime 在 30 秒内,就认为新用户;
+ @Override
+ public boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser) {
+ // TODO @疯狂:userId 为空,搞到参数校验里哇;
+ if (userId == null) {
+ throw exception(0);
+ }
+
+ // 1. 获得分销用户
+ boolean isNewBrokerageUser = false;
+ BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId);
+ if (brokerageUser == null) { // 分销用户不存在的情况:1. 新注册;2. 旧数据;3. 分销功能关闭后又打开
+ isNewBrokerageUser = true;
+ brokerageUser = new BrokerageUserDO().setId(userId).setBrokerageEnabled(false).setBrokeragePrice(0).setFrozenPrice(0);
+ }
+
+ // 2.1 校验能否绑定
+ boolean validated = validateCanBindUser(brokerageUser, bindUserId, isNewUser);
+ if (!validated) {
+ return false;
+ }
+
+ // 2.2 绑定用户
+ if (isNewBrokerageUser) {
+ Integer enabledCondition = tradeConfigService.getTradeConfig().getBrokerageEnabledCondition();
+ if (BrokerageEnabledConditionEnum.ALL.getCondition().equals(enabledCondition)) { // 人人分销:用户默认就有分销资格
+ // TODO @疯狂:应该设置下 brokerageTime,而不是 bindUserTime
+ brokerageUser.setBrokerageEnabled(true).setBindUserTime(LocalDateTime.now());
+ }
+ // TODO @疯狂:这里是不是要设置 bindUserId、bindUserTime 字段哈;
+ brokerageUserMapper.insert(brokerageUser);
+ } else {
+ brokerageUserMapper.updateById(new BrokerageUserDO().setId(userId)
+ .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
+ }
+ return true;
+ }
+
+ // TODO @疯狂:validate 方法,一般不返回 true false,而是抛出异常;如果要返回 true false 这种,方法名字可以改成 isUserCanBind
+ private boolean validateCanBindUser(BrokerageUserDO user, Long bindUserId, Boolean isNewUser) {
+ // TODO @疯狂:bindUserId 为空,搞到参数校验里哇;
+ if (bindUserId == null) {
+ return false;
+ }
+
+ // 校验分销功能是否启用
+ TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
+ if (tradeConfig == null || BooleanUtil.isFalse(tradeConfig.getBrokerageEnabled())) {
+ return false;
+ }
+
+ // 校验绑定自己
+ if (Objects.equals(user.getId(), bindUserId)) {
+ throw exception(BROKERAGE_BIND_SELF);
+ }
+
+ // 校验要绑定的用户有无推广资格
+ BrokerageUserDO bindUser = brokerageUserMapper.selectById(bindUserId);
+ if (bindUser == null || BooleanUtil.isFalse(bindUser.getBrokerageEnabled())) {
+ throw exception(BROKERAGE_BIND_USER_NOT_ENABLED);
+ }
+
+ // 校验分佣模式:仅可后台手动设置推广员
+ if (BrokerageEnabledConditionEnum.ADMIN.getCondition().equals(tradeConfig.getBrokerageEnabledCondition())) {
+ throw exception(BROKERAGE_BIND_CONDITION_ADMIN);
+ }
+
+ // 校验分销关系绑定模式
+ if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
+ if (!BooleanUtil.isTrue(isNewUser)) {
+ throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定
+ }
+ } else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) {
+ if (user.getBindUserId() != null) {
+ throw exception(BROKERAGE_BIND_OVERRIDE); // 已绑定了推广人
+ }
+ }
+
+ // TODO @疯狂:这块是不是一直查询到根节点,中间不允许出现自己;就是不能形成环。虽然目前是 2 级,但是未来可能会改多级; = = 环的话,就会存在问题哈
+ // A->B->A:下级不能绑定自己的上级, A->B->C->A可以!!
+ if (Objects.equals(user.getId(), bindUser.getBindUserId())) {
+ throw exception(BROKERAGE_BIND_LOOP);
+ }
+ return true;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigService.java
new file mode 100644
index 000000000..1edb4f30b
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.trade.service.config;
+
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+
+import javax.validation.Valid;
+
+/**
+ * 交易中心配置 Service 接口
+ *
+ * @author owen
+ */
+public interface TradeConfigService {
+
+ /**
+ * 更新交易中心配置
+ *
+ * @param updateReqVO 更新信息
+ */
+ void saveTradeConfig(@Valid TradeConfigSaveReqVO updateReqVO);
+
+ /**
+ * 获得交易中心配置
+ *
+ * @return 交易中心配置
+ */
+ TradeConfigDO getTradeConfig();
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigServiceImpl.java
new file mode 100644
index 000000000..c859cdee6
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigServiceImpl.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.trade.service.config;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.config.TradeConfigMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 交易中心配置 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class TradeConfigServiceImpl implements TradeConfigService {
+
+ @Resource
+ private TradeConfigMapper tradeConfigMapper;
+
+ @Override
+ public void saveTradeConfig(TradeConfigSaveReqVO saveReqVO) {
+ // 存在,则进行更新
+ TradeConfigDO dbConfig = getTradeConfig();
+ if (dbConfig != null) {
+ tradeConfigMapper.updateById(TradeConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
+ return;
+ }
+ // 不存在,则进行插入
+ tradeConfigMapper.insert(TradeConfigConvert.INSTANCE.convert(saveReqVO));
+ }
+
+ @Override
+ public TradeConfigDO getTradeConfig() {
+ List list = tradeConfigMapper.selectList();
+ return CollectionUtils.getFirst(list);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
index f887b71e8..7893d62a9 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.service.order;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
@@ -25,12 +24,14 @@ import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
+import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
@@ -46,10 +47,14 @@ 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;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
+import cn.iocoder.yudao.module.trade.dal.redis.no.TradeOrderNoRedisDAO;
import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
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.brokerage.record.BrokerageRecordService;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.cart.CartService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
@@ -71,7 +76,7 @@ import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_UPDATE_PRICE_FAIL_EQUAL;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_UPDATE_PRICE_FAIL_PAID;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
@@ -89,6 +94,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private TradeOrderMapper tradeOrderMapper;
@Resource
private TradeOrderItemMapper tradeOrderItemMapper;
+ @Resource
+ private TradeOrderNoRedisDAO orderNoRedisDAO;
@Resource
private CartService cartService;
@@ -112,12 +119,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Resource
private BargainRecordApi bargainRecordApi;
@Resource
+ private SeckillActivityApi seckillActivityApi;
+ @Resource
+ private BargainActivityApi bargainActivityApi;
+ @Resource
private MemberUserApi memberUserApi;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberPointApi memberPointApi;
@Resource
+ private BrokerageRecordService brokerageRecordService;
+ @Resource
private ProductCommentApi productCommentApi;
@Resource
@@ -190,22 +203,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// TODO @puhui999:这个逻辑,先抽个小方法;未来要通过设计模式,把这些拼团之类的逻辑,抽象出去
// 拼团
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
- MemberUserRespDTO user = memberUserApi.getUser(userId);
- List recordRespDTOS = combinationRecordApi.getRecordListByUserIdAndActivityId(userId, createReqVO.getCombinationActivityId());
- // TODO 拼团一次应该只能选择一种规格的商品
- TradeOrderItemDO orderItemDO = orderItems.get(0);
- if (CollUtil.isNotEmpty(recordRespDTOS)) {
- List skuIds = convertList(recordRespDTOS, CombinationRecordRespDTO::getSkuId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
- List tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderIdAnSkuId(convertList(recordRespDTOS,
- CombinationRecordRespDTO::getOrderId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
- combinationRecordApi.validateCombinationLimitCount(createReqVO.getCombinationActivityId(),
- CollectionUtils.getSumValue(tradeOrderItemDOS, TradeOrderItemDO::getCount, Integer::sum), orderItemDO.getCount());
- }
-
- combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user));
+ createCombinationRecord(userId, createReqVO, orderItems, order);
}
// 3.2 秒杀的特殊逻辑
- // TODO 秒杀扣减库存是下单就扣除还是等待订单支付成功再扣除
if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
}
@@ -215,6 +215,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
return order;
}
+ private void createCombinationRecord(Long userId, AppTradeOrderCreateReqVO createReqVO, List orderItems, TradeOrderDO order) {
+ MemberUserRespDTO user = memberUserApi.getUser(userId);
+ List recordRespDTOS = combinationRecordApi.getRecordListByUserIdAndActivityId(userId, createReqVO.getCombinationActivityId());
+ // TODO 拼团一次应该只能选择一种规格的商品
+ TradeOrderItemDO orderItemDO = orderItems.get(0);
+ if (CollUtil.isNotEmpty(recordRespDTOS)) {
+ List skuIds = convertList(recordRespDTOS, CombinationRecordRespDTO::getSkuId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
+ List tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderIdAnSkuId(convertList(recordRespDTOS,
+ CombinationRecordRespDTO::getOrderId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
+ combinationRecordApi.validateCombinationLimitCount(createReqVO.getCombinationActivityId(),
+ CollectionUtils.getSumValue(tradeOrderItemDOS, TradeOrderItemDO::getCount, Integer::sum), orderItemDO.getCount());
+ }
+
+ combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user));
+ }
+
// TODO @puhui999:订单超时,自动取消;
/**
@@ -241,8 +257,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
address = validateAddress(userId, createReqVO.getAddressId());
}
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
+ String no = orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX);
order.setType(validateActivity(createReqVO));
- order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @puhui999: 参考支付订单,的 no 生成哈;
+ order.setNo(no);
order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
@@ -294,19 +311,23 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
TradeOrderDO tradeOrderDO, List orderItems,
TradePriceCalculateRespBO calculateRespBO) {
- // 下单时扣减商品库存
- // TODO @puhui999:扣库存,需要前置;
+ Integer count = getSumValue(orderItems, TradeOrderItemDO::getCount, Integer::sum);
// 1)如果是秒杀商品:额外扣减秒杀的库存;
- // 2)如果是拼团活动:额外扣减拼团的库存;
- // 3)如果是砍价活动:额外扣减砍价的库存;
- productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
-
- // 删除购物车商品
- Set cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
- if (CollUtil.isNotEmpty(cartIds)) {
- cartService.deleteCart(userId, cartIds);
+ if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), tradeOrderDO.getType())) {
+ SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
+ updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
+ updateStockReqDTO.setCount(count);
+ updateStockReqDTO.setItems(CollectionUtils.convertList(orderItems, item -> {
+ SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
+ item1.setSpuId(item.getSpuId());
+ item1.setSkuId(item.getSkuId());
+ item1.setCount(item.getCount());
+ return item1;
+ }));
+ seckillActivityApi.updateSeckillStock(updateStockReqDTO);
}
-
+ // 2)如果是砍价活动:额外扣减砍价的库存;
+ bargainActivityApi.updateBargainActivityStock(createReqVO.getBargainActivityId(), count);
// 扣减积分 TODO 芋艿:待实现,需要前置;
// 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
@@ -316,6 +337,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.setOrderId(tradeOrderDO.getId()));
}
+ // 下单时扣减商品库存
+ productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
+
+ // 删除购物车商品
+ Set cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
+ if (CollUtil.isNotEmpty(cartIds)) {
+ cartService.deleteCart(userId, cartIds);
+ }
+
// 生成预支付
createPayOrder(tradeOrderDO, orderItems, calculateRespBO);
@@ -352,8 +382,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 1、拼团活动
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
// 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
- combinationRecordApi.updateCombinationRecordStatus(new CombinationRecordUpdateStatusReqDTO().setUserId(order.getUserId())
- .setOrderId(order.getId()).setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()).setStartTime(LocalDateTime.now()));
+ combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now());
}
// TODO 芋艿:发送订单变化的消息
@@ -365,6 +394,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
// 增加用户经验
getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
+ // 增加用户佣金
+ getSelf().addBrokerageAsync(order.getUserId(), order.getId());
}
/**
@@ -466,11 +497,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
*/
private TradeOrderDO validateOrderDeliverable(Long id) {
TradeOrderDO order = validateOrderExists(id);
- // 校验订单是否是待发货状态
- // TODO @puhui999:已经发货,可以重新发货,修改信息;
- if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) {
- throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
- }
// 校验订单是否退款
if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
@@ -536,20 +562,35 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
}
@Override
+ // TODO @puhui999:考虑事务性
public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) {
// 校验交易订单
TradeOrderDO order = validateOrderExists(reqVO.getId());
if (order.getPayStatus()) {
throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
}
- // TODO @puhui999:如果改价,需要校验下是否真的变化;
+ if (ObjectUtil.equal(order.getAdjustPrice(), reqVO.getAdjustPrice())) {
+ throw exception(ORDER_UPDATE_PRICE_FAIL_EQUAL);
+ }
- // 更新
- // TODO @puhui999:TradeOrderItemDO 需要做 adjustPrice 的分摊;另外,支付订单那的价格,需要 update 下;
+ // TODO @puhui999:应该是按照 payPrice 分配;并且要考虑取余问题;payPrice 也要考虑,item 里的
+ List itemDOs = tradeOrderItemMapper.selectListByOrderId(order.getId());
+ // TradeOrderItemDO 需要做 adjustPrice 的分摊
+ int price = reqVO.getAdjustPrice() / itemDOs.size();
+ itemDOs.forEach(item -> {
+ item.setAdjustPrice(price);
+ });
+ // 更新 TradeOrderItem
+ // TODO @puhui999:不要整个对象去更新哈;应该 new 一下;
+ tradeOrderItemMapper.updateBatch(itemDOs);
+ // 更新订单
+ // TODO @puhui999:要考虑多次修改价格,不能单单的 payPrice + 价格;
TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO);
update.setPayPrice(update.getPayPrice() + update.getAdjustPrice());
// TODO @芋艿:改价时,赠送的积分,要不要做改动???
tradeOrderMapper.updateById(update);
+ // 更新支付订单
+ payOrderApi.updatePayOrderPriceById(order.getPayOrderId(), update.getPayPrice());
}
@Override
@@ -629,12 +670,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
}
- // TODO 芋艿:未来如果有分佣,需要更新相关分佣订单为已失效
-
// 扣减用户积分
getSelf().reduceUserPointAsync(order.getUserId(), orderRefundPrice, afterSaleId);
// 扣减用户经验
getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
+ // 更新分佣记录为已失效
+ getSelf().cancelBrokerageAsync(order.getUserId(), id);
}
@Override
@@ -742,6 +783,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
}
+
+ @Async
+ protected void addBrokerageAsync(Long userId, Long orderId) {
+ List orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
+ List list = convertList(orderItems,
+ item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
+ brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, list);
+ }
+
+ @Async
+ protected void cancelBrokerageAsync(Long userId, Long orderItemId) {
+ brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
+ }
+
/**
* 获得自身的代理对象,解决 AOP 生效问题
*
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImplTest.java
new file mode 100644
index 000000000..abf2b37c1
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImplTest.java
@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.record;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.BrokerageRecordMapper;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import org.junit.jupiter.api.Disabled;
+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.math.RoundingMode;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.hutool.core.util.RandomUtil.randomInt;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+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;
+
+// TODO @芋艿:单测后续看看
+/**
+ * {@link BrokerageRecordServiceImpl} 的单元测试类
+ *
+ * @author owen
+ */
+@Import(BrokerageRecordServiceImpl.class)
+public class BrokerageRecordServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private BrokerageRecordServiceImpl brokerageRecordService;
+ @Resource
+ private BrokerageRecordMapper brokerageRecordMapper;
+
+ @MockBean
+ private TradeConfigService tradeConfigService;
+ @MockBean
+ private BrokerageUserService brokerageUserService;
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetBrokerageRecordPage() {
+ // mock 数据
+ BrokerageRecordDO dbBrokerageRecord = randomPojo(BrokerageRecordDO.class, o -> { // 等会查询到
+ o.setUserId(null);
+ o.setBizType(null);
+ o.setStatus(null);
+ o.setCreateTime(null);
+ });
+ brokerageRecordMapper.insert(dbBrokerageRecord);
+ // 测试 userId 不匹配
+ brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setUserId(null)));
+ // 测试 bizType 不匹配
+ brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setBizType(null)));
+ // 测试 status 不匹配
+ brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setStatus(null)));
+ // 测试 createTime 不匹配
+ brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setCreateTime(null)));
+ // 准备参数
+ BrokerageRecordPageReqVO reqVO = new BrokerageRecordPageReqVO();
+ reqVO.setUserId(null);
+ reqVO.setBizType(null);
+ reqVO.setStatus(null);
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+ // 调用
+ PageResult pageResult = brokerageRecordService.getBrokerageRecordPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbBrokerageRecord, pageResult.getList().get(0));
+ }
+
+ @Test
+ public void testCalculatePrice_useFixedPrice() {
+ // mock 数据
+ Integer payPrice = randomInteger();
+ Integer percent = randomInt(1, 101);
+ Integer fixedPrice = randomInt();
+ // 调用
+ int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice);
+ // 断言
+ assertEquals(brokerage, fixedPrice);
+ }
+
+ @Test
+ public void testCalculatePrice_usePercent() {
+ // mock 数据
+ Integer payPrice = randomInteger();
+ Integer percent = randomInt(1, 101);
+ Integer fixedPrice = randomEle(new Integer[]{0, null});
+ System.out.println("fixedPrice=" + fixedPrice);
+ // 调用
+ int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice);
+ // 断言
+ assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue());
+ }
+
+ @Test
+ public void testCalculatePrice_equalsZero() {
+ // mock 数据
+ Integer payPrice = null;
+ Integer percent = null;
+ Integer fixedPrice = null;
+ // 调用
+ int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice);
+ // 断言
+ assertEquals(brokerage, 0);
+ }
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImplTest.java
new file mode 100644
index 000000000..1c505e1b4
--- /dev/null
+++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImplTest.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.BrokerageUserMapper;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// TODO @芋艿:单测后续看看
+/**
+ * {@link BrokerageUserServiceImpl} 的单元测试类
+ *
+ * @author owen
+ */
+@Import(BrokerageUserServiceImpl.class)
+public class BrokerageUserServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private BrokerageUserServiceImpl brokerageUserService;
+
+ @Resource
+ private BrokerageUserMapper brokerageUserMapper;
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetBrokerageUserPage() {
+ // mock 数据
+ BrokerageUserDO dbBrokerageUser = randomPojo(BrokerageUserDO.class, o -> { // 等会查询到
+ o.setBindUserId(null);
+ o.setBrokerageEnabled(null);
+ o.setCreateTime(null);
+ });
+ brokerageUserMapper.insert(dbBrokerageUser);
+ // 测试 brokerageUserId 不匹配
+ brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBindUserId(null)));
+ // 测试 brokerageEnabled 不匹配
+ brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBrokerageEnabled(null)));
+ // 测试 createTime 不匹配
+ brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setCreateTime(null)));
+ // 准备参数
+ BrokerageUserPageReqVO reqVO = new BrokerageUserPageReqVO();
+ reqVO.setBindUserId(null);
+ reqVO.setBrokerageEnabled(null);
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+ // 调用
+ PageResult pageResult = brokerageUserService.getBrokerageUserPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbBrokerageUser, pageResult.getList().get(0));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
index dfa4a5b42..f02fdcaf1 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
+++ b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
@@ -2,3 +2,5 @@ DELETE FROM trade_order;
DELETE FROM trade_order_item;
DELETE FROM trade_after_sale;
DELETE FROM trade_after_sale_log;
+DELETE FROM trade_brokerage_user;
+DELETE FROM trade_brokerage_record;
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
index e30b0d225..4c0e0fcea 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
@@ -125,3 +125,42 @@ CREATE TABLE IF NOT EXISTS "trade_after_sale_log" (
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '交易售后日志';
+
+CREATE TABLE IF NOT EXISTS "trade_brokerage_user"
+(
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "bind_user_id" bigint NOT NULL,
+ "bind_user_time" varchar,
+ "brokerage_enabled" bit NOT NULL,
+ "brokerage_time" varchar,
+ "price" int NOT NULL,
+ "frozen_price" int NOT NULL,
+ "creator" varchar DEFAULT '',
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar DEFAULT '',
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT '0',
+ PRIMARY KEY ("id")
+) COMMENT '分销用户';
+CREATE TABLE IF NOT EXISTS "trade_brokerage_record"
+(
+ "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "user_id" bigint NOT NULL,
+ "biz_id" varchar NOT NULL,
+ "biz_type" varchar NOT NULL,
+ "title" varchar NOT NULL,
+ "price" int NOT NULL,
+ "total_price" int NOT NULL,
+ "description" varchar NOT NULL,
+ "status" varchar NOT NULL,
+ "frozen_days" int NOT NULL,
+ "unfreeze_time" varchar,
+ "creator" varchar DEFAULT '',
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar DEFAULT '',
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint not null default '0',
+ PRIMARY KEY ("id")
+) COMMENT '佣金记录';
\ No newline at end of file
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
index 51252530b..648802b80 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
@@ -28,6 +28,17 @@ tenant-id: {{appTenentId}}
"code": 9999
}
+### 请求 /social-login 接口 => 成功
+POST {{appApi}}/member/auth/social-login
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+{
+ "type": 34,
+ "code": "0e1oc9000CTjFQ1oim200bhtb61oc90g",
+ "state": "default"
+}
+
### 请求 /weixin-mini-app-login 接口 => 成功
POST {{appApi}}/member/auth/weixin-mini-app-login
Content-Type: application/json
@@ -38,7 +49,6 @@ tenant-id: {{appTenentId}}
"loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR"
}
-
### 请求 /logout 接口 => 成功
POST {{appApi}}/member/auth/logout
Content-Type: application/json
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
index 41318fe59..072ec9e4b 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
@@ -27,4 +27,12 @@ public class AppAuthLoginRespVO {
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime expiresTime;
+ /**
+ * 仅社交登录、社交绑定时会返回
+ *
+ * 为什么需要返回?微信公众号、微信小程序支付需要传递 openid 给支付接口
+ */
+ @Schema(description = "社交用户 openid", example = "qq768")
+ private String openid;
+
}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
index c957ceedc..9322f9146 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
@@ -29,7 +29,6 @@ public class AppMemberUserController {
@Resource
private MemberUserService userService;
-
@Resource
private MemberLevelService levelService;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
index fc3f427ca..25cceedc2 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
@@ -29,6 +29,9 @@ public class AppMemberUserInfoRespVO {
@Schema(description = "用户等级")
private Level level;
+ @Schema(description = "是否成为推广员", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean brokerageEnabled;
+
@Schema(description = "用户 App - 会员等级")
@Data
public static class Level {
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
index 16a8e6d4c..08c9b59ea 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
@@ -25,7 +25,7 @@ public interface AuthConvert {
SmsCodeUseReqDTO convert(AppMemberUserResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
- AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
+ AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean, String openid);
SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean);
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
index 94fed9c53..4a6354b03 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
@@ -6,14 +6,12 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
-import java.math.BigDecimal;
-
/**
* 会员积分配置 DO
*
* @author QingX
*/
-@TableName("member_point_config")
+@TableName(value = "member_point_config", autoResultMap = true)
@KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
index ed89d0a50..e8f816ea6 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
@@ -20,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
@@ -65,13 +66,14 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
// 如果 socialType 非空,说明需要绑定社交用户
+ String openid = null;
if (reqVO.getSocialType() != null) {
- socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+ openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌,记录登录日志
- return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
+ return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, openid);
}
@Override
@@ -86,32 +88,33 @@ public class MemberAuthServiceImpl implements MemberAuthService {
Assert.notNull(user, "获取用户失败,结果为空");
// 如果 socialType 非空,说明需要绑定社交用户
+ String openid = null;
if (reqVO.getSocialType() != null) {
- socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+ openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌,记录登录日志
- return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
+ return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, openid);
}
@Override
public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) {
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
- Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
+ SocialUserRespDTO socialUser = socialUserApi.getSocialUser(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
reqVO.getCode(), reqVO.getState());
- if (userId == null) {
+ if (socialUser == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 自动登录
- MemberUserDO user = userService.getUser(userId);
+ MemberUserDO user = userService.getUser(socialUser.getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
// 创建 Token 令牌,记录登录日志
- return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
+ return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, socialUser.getOpenid());
}
@Override
@@ -129,14 +132,15 @@ public class MemberAuthServiceImpl implements MemberAuthService {
Assert.notNull(user, "获取用户失败,结果为空");
// 绑定社交用户
- socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+ String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), ""));
// 创建 Token 令牌,记录登录日志
- return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
+ return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid);
}
- private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
+ private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile,
+ LoginLogTypeEnum logType, String openid) {
// 插入登陆日志
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
// 创建 Token 令牌
@@ -144,7 +148,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
.setUserId(user.getId()).setUserType(getUserType().getValue())
.setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
// 构建返回结果
- return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
+ return AuthConvert.INSTANCE.convert(accessTokenRespDTO, openid);
}
@Override
@@ -231,7 +235,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
public AppAuthLoginRespVO refreshToken(String refreshToken) {
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken,
OAuth2ClientConstants.CLIENT_ID_DEFAULT);
- return AuthConvert.INSTANCE.convert(accessTokenDO);
+ return AuthConvert.INSTANCE.convert(accessTokenDO, null);
}
private void createLogoutLog(Long userId) {
diff --git a/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql b/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
index c1553fe14..782a81810 100644
--- a/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
@@ -90,3 +90,24 @@ CREATE TABLE IF NOT EXISTS "member_group"
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '用户分组';
+CREATE TABLE IF NOT EXISTS "member_brokerage_record"
+(
+ "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "user_id" bigint NOT NULL,
+ "biz_id" varchar NOT NULL,
+ "biz_type" varchar NOT NULL,
+ "title" varchar NOT NULL,
+ "price" int NOT NULL,
+ "total_price" int NOT NULL,
+ "description" varchar NOT NULL,
+ "status" varchar NOT NULL,
+ "frozen_days" int NOT NULL,
+ "unfreeze_time" varchar,
+ "creator" varchar DEFAULT '',
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar DEFAULT '',
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint not null default '0',
+ PRIMARY KEY ("id")
+) COMMENT '佣金记录';
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java
index 5c1905ebe..b46f19534 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java
@@ -29,4 +29,13 @@ public interface PayOrderApi {
*/
PayOrderRespDTO getOrder(Long id);
+ // TODO @puhui999:可以去掉 byId;然后 payOrderId 参数改成 id;
+ /**
+ * 更新支付订单价格
+ *
+ * @param payOrderId 支付单编号
+ * @param payPrice 支付单价格
+ */
+ void updatePayOrderPriceById(Long payOrderId, Integer payPrice);
+
}
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
index 528e320d0..0f61d2d92 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
@@ -28,6 +28,7 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}");
ErrorCode ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款");
ErrorCode ORDER_UPDATE_PRICE_FAIL_PAID = new ErrorCode(1_007_002_006, "支付订单调价失败,原因:支付订单已付款,不能调价");
+ ErrorCode ORDER_UPDATE_PRICE_FAIL_EQUAL = new ErrorCode(1007002007, "支付订单调价失败,原因:价格没有变化");
// ========== ORDER 模块(拓展单) 1-007-003-000 ==========
ErrorCode ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在");
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java
index a245880ba..1740e3bba 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java
@@ -31,4 +31,9 @@ public class PayOrderApiImpl implements PayOrderApi {
return PayOrderConvert.INSTANCE.convert2(order);
}
+ @Override
+ public void updatePayOrderPriceById(Long payOrderId, Integer payPrice) {
+ payOrderService.updatePayOrderPriceById(payOrderId, payPrice);
+ }
+
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
index 9a2afbe66..896742636 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
@@ -5,13 +5,16 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.module.pay.framework.pay.wallet.WalletPayClient;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
+import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,11 +29,14 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
@Tag(name = "管理后台 - 支付订单")
@RestController
@@ -70,6 +76,16 @@ public class PayOrderController {
@PostMapping("/submit")
@Operation(summary = "提交支付订单")
public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
+ // 1. 钱包支付事,需要额外传 user_id 和 user_type
+ if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
+ Map channelExtras = reqVO.getChannelExtras() == null ?
+ Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
+ channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
+ channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
+ reqVO.setChannelExtras(channelExtras);
+ }
+
+ // 2. 提交支付
PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP());
return success(respVO);
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java
index 23515ed33..95c085cbc 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java
@@ -37,7 +37,7 @@ public class AppPayWalletController {
@Operation(summary = "获取钱包")
@PreAuthenticated
public CommonResult getPayWallet() {
- PayWalletDO wallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
+ PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
return success(PayWalletConvert.INSTANCE.convert(wallet));
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java
index b89628bc2..9d17c346e 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java
@@ -15,9 +15,6 @@ public class AppPayWalletTransactionRespVO {
@Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer bizType;
- @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
- private LocalDateTime transactionTime;
-
@Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long price;
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java
index c66cda871..bd0e0b9d7 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java
@@ -11,9 +11,9 @@ public class AppPayWalletRespVO {
private Integer balance;
@Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
- private Long totalExpense;
+ private Integer totalExpense;
@Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
- private Long totalRecharge;
+ private Integer totalRecharge;
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
index 9d1edaf6a..f806168c8 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.convert.wallet;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@@ -12,4 +13,7 @@ public interface PayWalletTransactionConvert {
PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class);
PageResult convertPage(PageResult page);
+
+ PayWalletTransactionDO convert(CreateWalletTransactionBO bean);
+
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
index 92eb620da..4536ae635 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
@@ -45,10 +45,10 @@ public class PayWalletDO extends BaseDO {
/**
* 累计支出,单位分
*/
- private Long totalExpense;
+ private Integer totalExpense;
/**
* 累计充值,单位分
*/
- private Long totalRecharge;
+ private Integer totalRecharge;
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
index 677febb25..04a869f32 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
@@ -7,8 +7,6 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
-import java.time.LocalDateTime;
-
/**
* 会员钱包流水 DO
*
@@ -24,6 +22,7 @@ public class PayWalletTransactionDO extends BaseDO {
*/
@TableId
private Long id;
+
/**
* 流水号
*/
@@ -42,34 +41,26 @@ public class PayWalletTransactionDO extends BaseDO {
* 枚举 {@link PayWalletBizTypeEnum#getType()}
*/
private Integer bizType;
- // TODO @jason:使用 string;因为可能有业务是 string 接入哈。
+
/**
* 关联业务编号
*/
- private Long bizId;
+ private String bizId;
- // TODO @jason:想了下,改成 title;流水标题;因为账户明细那,会看到这个;
/**
- * 附加说明
+ * 流水说明
*/
- private String description;
+ private String title;
- // TODO @jason:使用 price 哈。项目里,金额都是用这个为主;
/**
* 交易金额,单位分
*
* 正值表示余额增加,负值表示余额减少
*/
- private Integer amount;
+ private Integer price;
+
/**
* 交易后余额,单位分
*/
private Integer balance;
-
- // TODO @jason:使用 createTime 就够啦
- /**
- * 交易时间
- */
- private LocalDateTime transactionTime;
-
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
index a05b88fe5..ef695c9fe 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.dal.mysql.wallet;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@@ -12,6 +13,35 @@ public interface PayWalletMapper extends BaseMapperX {
return selectOne(PayWalletDO::getUserId, userId,
PayWalletDO::getUserType, userType);
}
+
+ /**
+ * 当消费退款时候, 更新钱包
+ *
+ * @param price 消费金额
+ * @param id 钱包 id
+ */
+ default int updateWhenConsumptionRefund(Integer price, Long id){
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" balance = balance + " + price
+ + ", total_expense = total_expense - " + price)
+ .eq(PayWalletDO::getId, id);
+ return update(null, lambdaUpdateWrapper);
+ }
+
+ /**
+ * 当消费时候, 更新钱包
+ *
+ * @param price 消费金额
+ * @param id 钱包 id
+ */
+ default int updateWhenConsumption(Integer price, Long id){
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" balance = balance - " + price
+ + ", total_expense = total_expense + " + price)
+ .eq(PayWalletDO::getId, id)
+ .ge(PayWalletDO::getBalance, price); // cas 逻辑
+ return update(null, lambdaUpdateWrapper);
+ }
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
index 9e08b2caa..7831e77bd 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
@@ -18,9 +18,9 @@ public interface PayWalletTransactionMapper extends BaseMapperX query = new LambdaQueryWrapperX()
.eq(PayWalletTransactionDO::getWalletId, walletId);
if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_INCOME)) {
- query.gt(PayWalletTransactionDO::getAmount, 0);
+ query.gt(PayWalletTransactionDO::getPrice, 0);
} else if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_EXPENSE)) {
- query.lt(PayWalletTransactionDO::getAmount, 0);
+ query.lt(PayWalletTransactionDO::getPrice, 0);
}
query.orderByDesc(PayWalletTransactionDO::getId);
return selectPage(pageReqVO, query);
@@ -30,9 +30,8 @@ public interface PayWalletTransactionMapper extends BaseMapperX {
+ public static final String USER_ID_KEY = "user_id";
+ public static final String USER_TYPE_KEY = "user_type";
+
private PayWalletService wallService;
+ private PayWalletTransactionService walletTransactionService;
+ private PayOrderService orderService;
+ private PayRefundService refundService;
public WalletPayClient(Long channelId, NonePayClientConfig config) {
super(channelId, PayChannelEnum.WALLET.getCode(), config);
@@ -36,14 +54,22 @@ public class WalletPayClient extends AbstractPayClient {
if (wallService == null) {
wallService = SpringUtil.getBean(PayWalletService.class);
}
+ if (walletTransactionService == null) {
+ walletTransactionService = SpringUtil.getBean(PayWalletTransactionService.class);
+ }
}
@Override
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
try {
- PayWalletTransactionDO transaction = wallService.pay(reqDTO.getOutTradeNo(), reqDTO.getPrice());
+ Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY);
+ Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY);
+ Assert.notNull(userId, "用户 id 不能为空");
+ Assert.notNull(userType, "用户类型不能为空");
+ PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(),
+ reqDTO.getPrice());
return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
- transaction.getTransactionTime(),
+ transaction.getCreateTime(),
reqDTO.getOutTradeNo(), transaction);
} catch (Throwable ex) {
log.error("[doUnifiedOrder] 失败", ex);
@@ -66,15 +92,39 @@ public class WalletPayClient extends AbstractPayClient {
@Override
protected PayOrderRespDTO doGetOrder(String outTradeNo) {
- throw new UnsupportedOperationException("待实现");
+ if (orderService == null) {
+ orderService = SpringUtil.getBean(PayOrderService.class);
+ }
+ PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
+ // 支付交易拓展单不存在, 返回关闭状态
+ if (orderExtension == null) {
+ return PayOrderRespDTO.closedOf(String.valueOf(ORDER_EXTENSION_NOT_FOUND.getCode()),
+ ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, "");
+ }
+ // 关闭状态
+ if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) {
+ return PayOrderRespDTO.closedOf(orderExtension.getChannelErrorCode(),
+ orderExtension.getChannelErrorMsg(), outTradeNo, "");
+ }
+ // 成功状态
+ if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
+ PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
+ String.valueOf(orderExtension.getOrderId()), PayWalletBizTypeEnum.PAYMENT);
+ Assert.notNull(walletTransaction, "支付单 {} 钱包流水不能为空", outTradeNo);
+ return PayOrderRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreator(),
+ walletTransaction.getCreateTime(), outTradeNo, walletTransaction);
+ }
+ // 其它状态为无效状态
+ log.error("[doGetOrder] 支付单 {} 的状态不正确", outTradeNo);
+ throw new IllegalStateException(String.format("支付单[%s] 状态不正确", outTradeNo));
}
@Override
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
try {
- PayWalletTransactionDO payWalletTransaction = wallService.refund(reqDTO.getOutRefundNo(),
+ PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
reqDTO.getRefundPrice(), reqDTO.getReason());
- return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getTransactionTime(),
+ return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(),
reqDTO.getOutRefundNo(), payWalletTransaction);
} catch (Throwable ex) {
log.error("[doUnifiedRefund] 失败", ex);
@@ -97,7 +147,31 @@ public class WalletPayClient extends AbstractPayClient {
@Override
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
- throw new UnsupportedOperationException("待实现");
+ if (refundService == null) {
+ refundService = SpringUtil.getBean(PayRefundService.class);
+ }
+ PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
+ // 支付退款单不存在, 返回退款失败状态
+ if (payRefund == null) {
+ return PayRefundRespDTO.failureOf(String.valueOf(REFUND_NOT_FOUND), REFUND_NOT_FOUND.getMsg(),
+ outRefundNo, "");
+ }
+ // 退款失败
+ if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
+ return PayRefundRespDTO.failureOf(payRefund.getChannelErrorCode(), payRefund.getChannelErrorMsg(),
+ outRefundNo, "");
+ }
+ // 退款成功
+ if (PayRefundStatusRespEnum.isSuccess(payRefund.getStatus())) {
+ PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
+ String.valueOf(payRefund.getId()), PayWalletBizTypeEnum.PAYMENT_REFUND);
+ Assert.notNull(walletTransaction, "支付退款单 {} 钱包流水不能为空", outRefundNo);
+ return PayRefundRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(),
+ outRefundNo, walletTransaction);
+ }
+ // 其它状态为无效状态
+ log.error("[doGetRefund] 支付退款单 {} 的状态不正确", outRefundNo);
+ throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo));
}
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
index 3d66ae9b1..e03bda117 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
@@ -33,7 +33,7 @@ public interface PayOrderService {
/**
* 获得支付订单
*
- * @param appId 应用编号
+ * @param appId 应用编号
* @param merchantOrderId 商户订单编号
* @return 支付订单
*/
@@ -75,7 +75,7 @@ public interface PayOrderService {
* 提交支付
* 此时,会发起支付渠道的调用
*
- * @param reqVO 提交请求
+ * @param reqVO 提交请求
* @param userIp 提交 IP
* @return 提交结果
*/
@@ -93,11 +93,19 @@ public interface PayOrderService {
/**
* 更新支付订单的退款金额
*
- * @param id 编号
+ * @param id 编号
* @param incrRefundPrice 增加的退款金额
*/
void updateOrderRefundPrice(Long id, Integer incrRefundPrice);
+ /**
+ * 更新支付订单价格
+ *
+ * @param payOrderId 支付单编号
+ * @param payPrice 支付单价格
+ */
+ void updatePayOrderPriceById(Long payOrderId, Integer payPrice);
+
/**
* 获得支付订单
*
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
index 944df1a10..f172eb187 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
@@ -31,7 +32,6 @@ import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
-import cn.iocoder.yudao.module.pay.util.MoneyUtils;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -411,6 +411,18 @@ public class PayOrderServiceImpl implements PayOrderService {
}
}
+ @Override
+ public void updatePayOrderPriceById(Long payOrderId, Integer payPrice) {
+ // TODO @puhui999:不能直接这样修改哈;应该只有未支付状态的订单才可以改;另外,如果价格如果没变,可以直接 return 哈;
+ PayOrderDO order = orderMapper.selectById(payOrderId);
+ if (order == null) {
+ throw exception(ORDER_NOT_FOUND);
+ }
+
+ order.setPrice(payPrice);
+ orderMapper.updateById(order);
+ }
+
@Override
public PayOrderExtensionDO getOrderExtension(Long id) {
return orderExtensionMapper.selectById(id);
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
index 2f57fc465..28f9849b5 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
/**
* 钱包 Service 接口
@@ -10,30 +11,59 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
*/
public interface PayWalletService {
- // TODO @jason:改成 getOrCreateWallet;因为目前解耦,用户注册时,不会创建钱包;需要这里兜底处理;
/**
* 获取钱包信息
*
+ * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包
+ *
* @param userId 用户编号
* @param userType 用户类型
*/
- PayWalletDO getPayWallet(Long userId, Integer userType);
+ PayWalletDO getOrCreateWallet(Long userId, Integer userType);
/**
- * 钱包支付
+ * 钱包订单支付
*
+ * @param userId 用户 id
+ * @param userType 用户类型
* @param outTradeNo 外部订单号
* @param price 金额
*/
- PayWalletTransactionDO pay(String outTradeNo, Integer price);
+ PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
/**
- * 钱包支付退款
+ * 钱包订单支付退款
*
* @param outRefundNo 外部退款号
* @param refundPrice 退款金额
* @param reason 退款原因
*/
- PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason);
+ PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason);
+
+ /**
+ * 扣减钱包余额
+ *
+ * @param userId 用户 id
+ * @param userType 用户类型
+ * @param bizId 业务关联 id
+ * @param bizType 业务关联分类
+ * @param price 扣减金额
+ * @return 钱包流水
+ */
+ PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,
+ Long bizId, PayWalletBizTypeEnum bizType, Integer price);
+
+ /**
+ * 增加钱包余额
+ *
+ * @param userId 用户 id
+ * @param userType 用户类型
+ * @param bizId 业务关联 id
+ * @param bizType 业务关联分类
+ * @param price 增加金额
+ * @return 钱包流水
+ */
+ PayWalletTransactionDO addWalletBalance(Long userId, Integer userType,
+ Long bizId, PayWalletBizTypeEnum bizType, Integer price);
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
index 6992d63f6..f09dfc618 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
@@ -1,13 +1,15 @@
package cn.iocoder.yudao.module.pay.service.wallet;
+import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
-import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
+import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -17,8 +19,6 @@ import javax.annotation.Resource;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
-import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT;
import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT_REFUND;
@@ -32,105 +32,55 @@ import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYM
@Slf4j
public class PayWalletServiceImpl implements PayWalletService {
- /**
- * 余额支付的 no 前缀
- */
- private static final String WALLET_PAY_NO_PREFIX = "WP";
- /**
- * 余额退款的 no 前缀
- */
- private static final String WALLET_REFUND_NO_PREFIX = "WR";
-
@Resource
- private PayWalletMapper payWalletMapper;
+ private PayWalletMapper walletMapper;
@Resource
- private PayNoRedisDAO noRedisDAO;
-
- @Resource
- private PayWalletTransactionService payWalletTransactionService;
+ private PayWalletTransactionService walletTransactionService;
@Resource
@Lazy
- private PayOrderService payOrderService;
+ private PayOrderService orderService;
@Resource
@Lazy
- private PayRefundService payRefundService;
+ private PayRefundService refundService;
@Override
- public PayWalletDO getPayWallet(Long userId, Integer userType) {
- return payWalletMapper.selectByUserIdAndType(userId, userType);
+ public PayWalletDO getOrCreateWallet(Long userId, Integer userType) {
+ PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType);
+ if (wallet == null) {
+ wallet = new PayWalletDO().setUserId(userId).setUserType(userType)
+ .setBalance(0).setTotalExpense(0).setTotalRecharge(0);
+ wallet.setCreateTime(LocalDateTime.now());
+ walletMapper.insert(wallet);
+ }
+ return wallet;
}
- // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);reduceWalletBalance;
- // TODO @jason:最好是,明确传入哪个 userId 或者 walletId;
@Override
@Transactional(rollbackFor = Exception.class)
- public PayWalletTransactionDO pay(String outTradeNo, Integer price) {
- // 1.1 判断支付交易拓展单是否存
- PayOrderExtensionDO orderExtension = payOrderService.getOrderExtensionByNo(outTradeNo);
+ public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) {
+ // 1. 判断支付交易拓展单是否存
+ PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
if (orderExtension == null) {
throw exception(ORDER_EXTENSION_NOT_FOUND);
}
- // 1.2 判断余额是否足够
- PayWalletDO payWallet = validatePayWallet();
- int afterBalance = payWallet.getBalance() - price;
- if (afterBalance < 0) {
- throw exception(WALLET_BALANCE_NOT_ENOUGH);
- }
-
- // 2.1 扣除余额
- // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 > price,以及 - price
- payWallet.setBalance(afterBalance);
- payWallet.setTotalExpense(payWallet.getTotalExpense() + price);
- payWalletMapper.updateById(payWallet);
-
- // 2.2 生成钱包流水
- String walletNo = noRedisDAO.generate(WALLET_PAY_NO_PREFIX);
- PayWalletTransactionDO walletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())
- .setNo(walletNo).setAmount(price * -1).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())
- .setBizId(orderExtension.getOrderId()).setBizType(PAYMENT.getType());
- payWalletTransactionService.createWalletTransaction(walletTransaction);
- return walletTransaction;
+ // 2. 扣减余额
+ return reduceWalletBalance(userId, userType, orderExtension.getOrderId(), PAYMENT, price);
}
- // TODO @jason:不要在 service 里去使用用户上下文,这样和 request 就耦合了。
- private PayWalletDO validatePayWallet() {
- Long userId = getLoginUserId();
- Integer userType = getLoginUserType();
- PayWalletDO payWallet = getPayWallet(userId, userType);
- if (payWallet == null) {
- log.error("[validatePayWallet] 用户 {} 钱包不存在", userId);
- throw exception(WALLET_NOT_FOUND);
- }
- return payWallet;
- }
-
- // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);addWalletBalance;这样,如果后续充值,应该也是能复用这个方法的;
@Override
@Transactional(rollbackFor = Exception.class)
- public PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason) {
+ public PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason) {
// 1.1 判断退款单是否存在
- PayRefundDO payRefund = payRefundService.getRefundByNo(outRefundNo);
+ PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
if (payRefund == null) {
throw exception(REFUND_NOT_FOUND);
}
// 1.2 校验是否可以退款
- PayWalletDO payWallet = validatePayWallet();
- validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), payWallet.getId(), refundPrice);
-
- // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 + 金额
- Integer afterBalance = payWallet.getBalance() + refundPrice;
- payWallet.setBalance(afterBalance);
- payWallet.setTotalExpense(payWallet.getTotalExpense() + refundPrice * -1L);
- payWalletMapper.updateById(payWallet);
-
- // 2.2 生成钱包流水
- String walletNo = noRedisDAO.generate(WALLET_REFUND_NO_PREFIX);
- PayWalletTransactionDO newWalletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())
- .setNo(walletNo).setAmount(refundPrice).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())
- .setBizId(payRefund.getId()).setBizType(PAYMENT_REFUND.getType())
- .setDescription(reason);
- payWalletTransactionService.createWalletTransaction(newWalletTransaction);
- return newWalletTransaction;
+ Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), refundPrice);
+ PayWalletDO wallet = walletMapper.selectById(walletId);
+ Assert.notNull(wallet, "钱包 {} 不存在", walletId);
+ // 2. 增加余额
+ return addWalletBalance(wallet.getUserId(), wallet.getUserType(), payRefund.getId(), PAYMENT_REFUND, refundPrice);
}
/**
@@ -138,24 +88,78 @@ public class PayWalletServiceImpl implements PayWalletService {
*
* @param refundId 支付退款单 id
* @param walletPayNo 钱包支付 no
- * @param walletId 钱包 id
*/
- // TODO @jason:不要使用基本类型;
- private void validateWalletCanRefund(long refundId, String walletPayNo, long walletId, int refundPrice) {
- // 查询钱包支付交易
- PayWalletTransactionDO payWalletTransaction = payWalletTransactionService.getWalletTransactionByNo(walletPayNo);
- if (payWalletTransaction == null) {
+ private Long validateWalletCanRefund(Long refundId, String walletPayNo, Integer refundPrice) {
+ // 1. 校验钱包支付交易存在
+ PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransactionByNo(walletPayNo);
+ if (walletTransaction == null) {
throw exception(WALLET_TRANSACTION_NOT_FOUND);
}
// 原来的支付金额
- int amount = payWalletTransaction.getAmount() * -1; // TODO @jason:直接 - payWalletTransaction.getAmount() 即可;
+ // TODO @jason:应该允许多次退款哈;
+ int amount = - walletTransaction.getPrice();
if (refundPrice != amount) {
throw exception(WALLET_REFUND_AMOUNT_ERROR);
}
- PayWalletTransactionDO refundTransaction = payWalletTransactionService.getWalletTransaction(walletId, refundId, PAYMENT_REFUND);
+ PayWalletTransactionDO refundTransaction = walletTransactionService.getWalletTransaction(
+ String.valueOf(refundId), PAYMENT_REFUND);
if (refundTransaction != null) {
throw exception(WALLET_REFUND_EXIST);
}
+ return walletTransaction.getWalletId();
+ }
+
+ @Override
+ public PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,
+ Long bizId, PayWalletBizTypeEnum bizType, Integer price) {
+ // 1. 获取钱包
+ PayWalletDO payWallet = getOrCreateWallet(userId, userType);
+
+ // 2.1 扣除余额
+ int updateCounts = 0 ;
+ switch (bizType) {
+ case PAYMENT: {
+ updateCounts = walletMapper.updateWhenConsumption(price, payWallet.getId());
+ break;
+ }
+ case RECHARGE_REFUND: {
+ // TODO
+ break;
+ }
+ }
+ if (updateCounts == 0) {
+ throw exception(WALLET_BALANCE_NOT_ENOUGH);
+ }
+ // 2.2 生成钱包流水
+ Integer afterBalance = payWallet.getBalance() - price;
+ CreateWalletTransactionBO bo = new CreateWalletTransactionBO().setWalletId(payWallet.getId())
+ .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
+ .setBizType(bizType.getType()).setTitle(bizType.getDescription());
+ return walletTransactionService.createWalletTransaction(bo);
+ }
+
+ @Override
+ public PayWalletTransactionDO addWalletBalance(Long userId, Integer userType,
+ Long bizId, PayWalletBizTypeEnum bizType, Integer price) {
+ // 1. 获取钱包
+ PayWalletDO payWallet = getOrCreateWallet(userId, userType);
+ switch (bizType) {
+ case PAYMENT_REFUND: {
+ // 更新退款
+ walletMapper.updateWhenConsumptionRefund(price, payWallet.getId());
+ break;
+ }
+ case RECHARGE: {
+ //TODO
+ break;
+ }
+ }
+
+ // 2. 生成钱包流水
+ CreateWalletTransactionBO bo = new CreateWalletTransactionBO().setWalletId(payWallet.getId())
+ .setPrice(price).setBalance(payWallet.getBalance()+price).setBizId(String.valueOf(bizId))
+ .setBizType(bizType.getType()).setTitle(bizType.getDescription());
+ return walletTransactionService.createWalletTransaction(bo);
}
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
index e0714081b..52c84e159 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
@@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
+import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
+
+import javax.validation.Valid;
/**
* 钱包余额流水 Service 接口
@@ -25,10 +28,10 @@ public interface PayWalletTransactionService {
/**
* 新增钱包余额流水
*
- * @param payWalletTransaction 余额流水
- * @return id
+ * @param bo 创建钱包流水 bo
+ * @return 新建的钱包 do
*/
- Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction);
+ PayWalletTransactionDO createWalletTransaction(@Valid CreateWalletTransactionBO bo);
/**
* 根据 no,获取钱包余流水
@@ -40,11 +43,10 @@ public interface PayWalletTransactionService {
/**
* 获取钱包流水
*
- * @param walletId 钱包编号
* @param bizId 业务编号
* @param type 业务类型
* @return 钱包流水
*/
- PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type);
-
+ PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type);
+
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
index e2338219d..6ef32a557 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
@@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.pay.service.wallet;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper;
+import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
+import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;
-
/**
* 钱包流水 Service 实现类
*
@@ -23,27 +23,31 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FO
@Slf4j
public class PayWalletTransactionServiceImpl implements PayWalletTransactionService {
- @Resource
- private PayWalletService payWalletService;
+ /**
+ * 钱包流水的 no 前缀
+ */
+ private static final String WALLET_NO_PREFIX = "W";
+ @Resource
+ private PayWalletService payWalletService;
@Resource
private PayWalletTransactionMapper payWalletTransactionMapper;
+ @Resource
+ private PayNoRedisDAO noRedisDAO;
@Override
public PageResult getWalletTransactionPage(Long userId, Integer userType,
AppPayWalletTransactionPageReqVO pageVO) {
- PayWalletDO wallet = payWalletService.getPayWallet(userId, userType);
- if (wallet == null) {
- log.error("[getWalletTransactionPage][用户({}/{}) 钱包不存在", userId, userType);
- throw exception(WALLET_NOT_FOUND);
- }
+ PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO);
}
@Override
- public Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction) {
- payWalletTransactionMapper.insert(payWalletTransaction);
- return payWalletTransaction.getId();
+ public PayWalletTransactionDO createWalletTransaction(CreateWalletTransactionBO bo) {
+ PayWalletTransactionDO transaction = PayWalletTransactionConvert.INSTANCE.convert(bo)
+ .setNo(noRedisDAO.generate(WALLET_NO_PREFIX));
+ payWalletTransactionMapper.insert(transaction);
+ return transaction;
}
@Override
@@ -52,8 +56,8 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ
}
@Override
- public PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type) {
- return payWalletTransactionMapper.selectByWalletIdAndBiz(walletId, bizId, type.getType());
+ public PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type) {
+ return payWalletTransactionMapper.selectByBiz(bizId, type.getType());
}
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/CreateWalletTransactionBO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/CreateWalletTransactionBO.java
new file mode 100644
index 000000000..a1b7af8be
--- /dev/null
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/CreateWalletTransactionBO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.pay.service.wallet.bo;
+
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
+import lombok.Data;
+
+/**
+ * 创建钱包流水 BO
+ *
+ * @author jason
+ */
+@Data
+public class CreateWalletTransactionBO {
+
+ // TODO @jason:bo 的话,最好加个参数校验哈;
+
+ /**
+ * 钱包编号
+ *
+ */
+ private Long walletId;
+
+ /**
+ * 交易金额,单位分
+ *
+ * 正值表示余额增加,负值表示余额减少
+ */
+ private Integer price;
+
+ /**
+ * 交易后余额,单位分
+ */
+ private Integer balance;
+
+ /**
+ * 关联业务分类
+ *
+ * 枚举 {@link PayWalletBizTypeEnum#getType()}
+ */
+ private Integer bizType;
+
+ /**
+ * 关联业务编号
+ */
+ private String bizId;
+
+ /**
+ * 流水说明
+ */
+ private String title;
+}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
index 5d42731c2..c7c2fe459 100644
--- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.api.social;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@@ -27,8 +28,9 @@ public interface SocialUserApi {
* 绑定社交用户
*
* @param reqDTO 绑定信息
+ * @return 社交用户 openid
*/
- void bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
+ String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
/**
* 取消绑定社交用户
@@ -38,16 +40,17 @@ public interface SocialUserApi {
void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);
/**
- * 获得社交用户的绑定用户编号
- * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
+ * 获得社交用户
+ *
* 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
*
* @param userType 用户类型
* @param type 社交平台的类型
* @param code 授权码
* @param state state
- * @return 绑定用户编号
+ * @return 社交用户
*/
- Long getBindUserId(Integer userType, Integer type, String code, String state);
+ SocialUserRespDTO getSocialUser(Integer userType, Integer type,
+ String code, String state);
}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserRespDTO.java
new file mode 100644
index 000000000..ac25b148e
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserRespDTO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.system.api.social.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 社交用户 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SocialUserRespDTO {
+
+ /**
+ * 社交用户 openid
+ */
+ private String openid;
+
+ /**
+ * 关联的用户编号
+ */
+ private Long userId;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
index ae8903135..d322952af 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.api.social;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import org.springframework.stereotype.Service;
@@ -26,8 +27,8 @@ public class SocialUserApiImpl implements SocialUserApi {
}
@Override
- public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
- socialUserService.bindSocialUser(reqDTO);
+ public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
+ return socialUserService.bindSocialUser(reqDTO);
}
@Override
@@ -37,8 +38,8 @@ public class SocialUserApiImpl implements SocialUserApi {
}
@Override
- public Long getBindUserId(Integer userType, Integer type, String code, String state) {
- return socialUserService.getBindUserId(userType, type, code, state);
+ public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
+ return socialUserService.getSocialUser(userType, type, code, state);
}
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
index ca34156eb..37fac0997 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
@@ -155,14 +156,14 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Override
public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) {
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
- Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
+ SocialUserRespDTO socialUser = socialUserService.getSocialUser(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
reqVO.getCode(), reqVO.getState());
- if (userId == null) {
+ if (socialUser == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 获得用户
- AdminUserDO user = userService.getUser(userId);
+ AdminUserDO user = userService.getUser(socialUser.getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
index 6d89897bb..bc776ec60 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.social;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@@ -50,8 +51,9 @@ public interface SocialUserService {
* 绑定社交用户
*
* @param reqDTO 绑定信息
+ * @return 社交用户 openid
*/
- void bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
+ String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
/**
* 取消绑定社交用户
@@ -64,15 +66,16 @@ public interface SocialUserService {
void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
/**
- * 获得社交用户的绑定用户编号
- * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
+ * 获得社交用户
+ *
* 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
*
* @param userType 用户类型
* @param type 社交平台的类型
* @param code 授权码
* @param state state
- * @return 绑定用户编号
+ * @return 社交用户
*/
- Long getBindUserId(Integer userType, Integer type, String code, String state);
+ SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state);
+
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
index b6999bd01..bd5548af7 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@@ -98,7 +99,7 @@ public class SocialUserServiceImpl implements SocialUserService {
@Override
@Transactional
- public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
+ public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
// 获得社交用户
SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
Assert.notNull(socialUser, "社交用户不能为空");
@@ -115,6 +116,7 @@ public class SocialUserServiceImpl implements SocialUserService {
.userId(reqDTO.getUserId()).userType(reqDTO.getUserType())
.socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();
socialUserBindMapper.insert(socialUserBind);
+ return socialUser.getOpenid();
}
@Override
@@ -130,7 +132,7 @@ public class SocialUserServiceImpl implements SocialUserService {
}
@Override
- public Long getBindUserId(Integer userType, Integer type, String code, String state) {
+ public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
// 获得社交用户
SocialUserDO socialUser = authSocialUser(type, code, state);
Assert.notNull(socialUser, "社交用户不能为空");
@@ -141,7 +143,7 @@ public class SocialUserServiceImpl implements SocialUserService {
if (socialUserBind == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
- return socialUserBind.getUserId();
+ return new SocialUserRespDTO(socialUser.getOpenid(), socialUserBind.getUserId());
}
/**
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
index b0331cff2..1009e9220 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
@@ -235,8 +236,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class);
// mock 方法(绑定的用户编号)
Long userId = 1L;
- when(socialUserService.getBindUserId(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),
- eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(userId);
+ when(socialUserService.getSocialUser(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),
+ eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(new SocialUserRespDTO(randomString(), userId));
// mock(用户)
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));
when(userService.getUser(eq(userId))).thenReturn(user);
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
index fcea1a864..bfbbd40aa 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@@ -195,10 +196,11 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
.setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId()));
// 调用
- socialUserService.bindSocialUser(reqDTO);
+ String openid = socialUserService.bindSocialUser(reqDTO);
// 断言
List socialUserBinds = socialUserBindMapper.selectList();
assertEquals(1, socialUserBinds.size());
+ assertEquals(socialUser.getOpenid(), openid);
}
@Test
@@ -232,25 +234,26 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
}
@Test
- public void testGetBindUserId() {
+ public void testGetSocialUser() {
// 准备参数
Integer userType = UserTypeEnum.ADMIN.getValue();
Integer type = SocialTypeEnum.GITEE.getType();
String code = "tudou";
String state = "yuanma";
// mock 社交用户
- SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
- socialUserMapper.insert(socialUser);
+ SocialUserDO socialUserDO = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
+ socialUserMapper.insert(socialUserDO);
// mock 社交用户的绑定
Long userId = randomLong();
SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId)
- .setSocialType(type).setSocialUserId(socialUser.getId());
+ .setSocialType(type).setSocialUserId(socialUserDO.getId());
socialUserBindMapper.insert(socialUserBind);
// 调用
- Long result = socialUserService.getBindUserId(userType, type, code, state);
+ SocialUserRespDTO socialUser = socialUserService.getSocialUser(userType, type, code, state);
// 断言
- assertEquals(userId, result);
+ assertEquals(userId, socialUser.getUserId());
+ assertEquals(socialUserDO.getOpenid(), socialUser.getOpenid());
}
}
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index aaaa6878e..a20f27098 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -41,24 +41,24 @@
yudao-spring-boot-starter-biz-error-code