Browse Source

【代码优化】商城:优化分销提现的逻辑(余额)

YunaiV 2 months ago
parent
commit
87cee4628a

+ 0 - 3
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java

@@ -11,9 +11,6 @@ public interface MessageTemplateConstants {
 
     String SMS_ORDER_DELIVERY = "order_delivery"; // 短信模版编号
 
-    String SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过)
-    String SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过)
-
     // ======================= 小程序订阅消息模版 =======================
 
     String WXA_ORDER_DELIVERY = "订单发货通知";

+ 10 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java

@@ -38,4 +38,14 @@ public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
         return ARRAYS;
     }
 
+    /**
+     * 是否通过支付平台的 API 打款
+     *
+     * @param type 类型
+     * @return 是否
+     */
+    public static boolean isApi(Integer type) {
+        return WECHAT_API.getType().equals(type);
+    }
+
 }

+ 34 - 39
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java

@@ -9,8 +9,7 @@ import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
 import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
 import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
@@ -24,7 +23,6 @@ import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
-import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
@@ -33,6 +31,7 @@ import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSumma
 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
 import jakarta.annotation.Resource;
 import jakarta.validation.Validator;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -43,6 +42,7 @@ import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
 /**
@@ -52,6 +52,7 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
  */
 @Service
 @Validated
+@Slf4j
 public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
 
     @Resource
@@ -82,62 +83,55 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     public void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp) {
         // 1.1 校验存在
         BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
-
         // 1.2 校验状态为审核中
         if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) {
             throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
         }
 
-        // 2. 更新
+        // 2. 更新状态
         int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(),
                 new BrokerageWithdrawDO().setStatus(status.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
         if (rows == 0) {
             throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
         }
 
-        String templateCode;
+        // 3.1 审批通过的后续处理
         if (BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.equals(status)) {
-            templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE;
-            // 3.1 通过时佣金转余额
-            if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
-                // TODO 改成直接调用 addWallet 增加余额,不用查询 wallet
-                PayWalletRespDTO wallet = payWalletApi.getWalletByUserId(withdraw.getUserId());
-                payWalletApi.addWallet(new PayWalletCreateReqDto()
-                        .setWalletId(wallet.getId())
-                        .setBizType(PayWalletBizTypeEnum.WITHDRAW)
-                        .setBizId(withdraw.getId().toString())
-                        .setPrice(withdraw.getPrice())
-                        .setTitle("分佣提现"));
-                rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
-                        new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
-                if (rows == 0) {
-                    throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
-                }
-            } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
-                // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈?
-                Long payTransferId = createPayTransfer(userIp, withdraw);
-            }
+            auditBrokerageWithdrawSuccess(withdraw);
+        // 3.2 审批不通过的后续处理
         } else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
-            templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT;
-            // 3.2 驳回时需要退还用户佣金
             brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
                     String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
         } else {
             throw new IllegalArgumentException("不支持的提现状态:" + status);
         }
+    }
 
-        // TODO @luchi:这个通知,还是要的呀~~~
-        // 4. 通知用户
-//        Map<String, Object> templateParams = MapUtil.<String, Object>builder()
-//                .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
-//                .put("price", MoneyUtils.fenToYuanStr(withdraw.getPrice()))
-//                .put("reason", auditReason)
-//                .build();
-//        notifyMessageSendApi.sendSingleMessageToMember(new NotifySendSingleToUserReqDTO()
-//                .setUserId(withdraw.getUserId()).setTemplateCode(templateCode).setTemplateParams(templateParams));
+    private void auditBrokerageWithdrawSuccess(BrokerageWithdrawDO withdraw) {
+        // 1.1 钱包
+        if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
+            payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO()
+                    .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
+                    .setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString())
+                    .setPrice(withdraw.getPrice()));
+        // 1.2 微信 API
+        } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
+            // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈?
+            Long payTransferId = createPayTransfer(withdraw);
+        // 1.3 剩余类型,都是手动打款,所以不处理
+        } else {
+            // TODO 可优化:未来可以考虑,接入支付宝、银联等 API 转账,实现自动打款
+            log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,无需处理]", withdraw.getId(), withdraw.getType());
+        }
+
+        // 2. 非支付 API,则直接体现成功
+        if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
+            brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
+                    new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
+        }
     }
 
-    private Long createPayTransfer(String userIp, BrokerageWithdrawDO withdraw) {
+    private Long createPayTransfer(BrokerageWithdrawDO withdraw) {
         // 1.1 获取微信 openid
         SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
                 UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType());
@@ -149,7 +143,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
                 .setMerchantTransferId(withdraw.getId().toString())
                 .setPrice(withdraw.getPrice())
                 .setSubject("佣金提现")
-                .setOpenid(socialUser.getOpenid()).setUserIp(userIp);
+                .setOpenid(socialUser.getOpenid()).setUserIp(getClientIP());
         // 2. 发起请求
         return payTransferApi.createTransfer(payTransferCreateReqDTO);
     }
@@ -230,6 +224,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
         // TODO @luchi:建议参考支付那,即使成功的情况下,也要各种校验;金额是否匹配、转账单号是否匹配、是否重复调用;
         if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) {
             withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
+            // TODO @luchi:发送站内信
         } else if (PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) {
             // TODO @luchi:这里,是不是不用更新哈?
             withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());

+ 4 - 15
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.pay.api.wallet;
 
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
 
 /**
  * 钱包 API 接口
@@ -10,21 +9,11 @@ import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
  */
 public interface PayWalletApi {
 
-    // TODO @luchi:1)改成 addWalletBalance;2)PayWalletCreateReqDto 搞成 userId、userType;3)bizType 使用 integer,不然后续挪到 cloud 不好弄,因为枚举不好序列化
     /**
-     * 添加钱包
+     * 添加钱包余额
      *
-     * @param reqDTO 创建请求
+     * @param reqDTO 增加余额请求
      */
-    void addWallet(PayWalletCreateReqDto reqDTO);
-
-    // TODO @luchi:不用去 getWalletByUserId 钱包,直接添加余额就好。里面内部去创建。如果删除掉的化,PayWalletRespDTO 也删除哈。
-    /**
-     * 根据用户编号,获取钱包信息
-     *
-     * @param userId 用户id
-     * @return 钱包信息
-     */
-    PayWalletRespDTO getWalletByUserId(Long userId);
+    void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO);
 
 }

+ 20 - 13
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletCreateReqDto.java → yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java

@@ -1,36 +1,43 @@
 package cn.iocoder.yudao.module.pay.api.wallet.dto;
 
-import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+/**
+ * 钱包余额增加 Request DTO
+ *
+ * @author 芋道源码
+ */
 @Data
-public class PayWalletCreateReqDto {
+public class PayWalletAddBalanceReqDTO {
 
     /**
-     * 钱包编号
+     * 用户编号
+     *
+     * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+    /**
+     * 用户类型
+     *
+     * 关联 {@link  UserTypeEnum}
      */
-    @NotNull(message = "钱包编号不能为空")
-    private Long walletId;
+    @NotNull(message = "用户类型不能为空")
+    private Integer userType;
 
     /**
      * 关联业务分类
      */
     @NotNull(message = "关联业务分类不能为空")
-    private PayWalletBizTypeEnum bizType;
-
+    private Integer bizType;
     /**
      * 关联业务编号
      */
     @NotNull(message = "关联业务编号不能为空")
     private String bizId;
 
-    /**
-     * 流水说明
-     */
-    @NotNull(message = "流水说明不能为空")
-    private String title;
-
     /**
      * 交易金额,单位分
      *

+ 0 - 18
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java

@@ -1,18 +0,0 @@
-package cn.iocoder.yudao.module.pay.api.wallet.dto;
-
-import lombok.Data;
-
-@Data
-public class PayWalletRespDTO {
-
-    /**
-     * 编号
-     */
-    private Long id;
-
-    /**
-     * 余额,单位分
-     */
-    private Integer balance;
-
-}

+ 5 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java

@@ -20,7 +20,7 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
     PAYMENT(3, "支付"),
     PAYMENT_REFUND(4, "支付退款"),
     UPDATE_BALANCE(5, "更新余额"),
-    WITHDRAW(6, "分佣提现");
+    BROKERAGE_WITHDRAW(6, "分佣提现");
 
     /**
      * 业务分类
@@ -38,4 +38,8 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
          return ARRAYS;
     }
 
+    public static PayWalletBizTypeEnum valueOf(Integer type) {
+        return Arrays.stream(values()).filter(item -> item.getType().equals(type)).findFirst().orElse(null);
+    }
+
 }

+ 11 - 11
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java

@@ -1,10 +1,9 @@
 package cn.iocoder.yudao.module.pay.api.wallet;
 
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
@@ -21,13 +20,14 @@ public class PayWalletApiImpl implements PayWalletApi {
     private PayWalletService payWalletService;
 
     @Override
-    public void addWallet(PayWalletCreateReqDto reqDTO) {
-        payWalletService.addWalletBalance(reqDTO.getWalletId(), reqDTO.getBizId(), reqDTO.getBizType(), reqDTO.getPrice());
-    }
+    public void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO) {
+        // 创建或获取钱包
+        PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), reqDTO.getUserType());
+        Assert.notNull(wallet, "钱包({}/{})不存在", reqDTO.getUserId(), reqDTO.getUserType());
 
-    @Override
-    public PayWalletRespDTO getWalletByUserId(Long userId) {
-        PayWalletDO orCreateWallet = payWalletService.getOrCreateWallet(userId, UserTypeEnum.MEMBER.getValue());
-        return BeanUtils.toBean(orCreateWallet, PayWalletRespDTO.class);
+        // 增加余额
+        PayWalletBizTypeEnum bizType = PayWalletBizTypeEnum.valueOf(reqDTO.getBizType());
+        payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice());
     }
+
 }

+ 13 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java

@@ -68,6 +68,19 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
         return update(null, lambdaUpdateWrapper);
     }
 
+    /**
+     * 增加余额的时候,更新钱包
+     *
+     * @param id 钱包 id
+     * @param price 钱包金额
+     */
+    default void updateWhenAdd(Long id, Integer price) {
+        LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
+             .setSql(" balance = balance + " + price)
+             .eq(PayWalletDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
     /**
      * 冻结钱包部分余额
      *

+ 5 - 9
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java

@@ -187,7 +187,7 @@ public class PayWalletServiceImpl implements PayWalletService {
 
         // 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
         return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
-            // 2. 更新钱包金额
+            // 3. 更新钱包金额
             switch (bizType) {
                 case PAYMENT_REFUND: { // 退款更新
                     walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
@@ -198,19 +198,15 @@ public class PayWalletServiceImpl implements PayWalletService {
                     break;
                 }
                 case UPDATE_BALANCE: // 更新余额
-                    walletMapper.updateWhenRecharge(payWallet.getId(), price);
-                    break;
-                // TODO @luchi:1)不能使用 updateWhenRecharge,它是充值哈。应该增加余额就 ok 了。。ps:顺便帮我把 UPDATE_BALANCE 也改改哈;2)其实不用 WITHDRAW 枚举,复用 UPDATE_BALANCE 就好了。
-                case WITHDRAW:
-                    walletMapper.updateWhenRecharge(payWallet.getId(), price);
+                case BROKERAGE_WITHDRAW: // 分佣提现
+                    walletMapper.updateWhenAdd(payWallet.getId(), price);
                     break;
                 default: {
-                    // TODO 其它类型待实现
-                    throw new UnsupportedOperationException("待实现");
+                    throw new UnsupportedOperationException("待实现:" + bizType);
                 }
             }
 
-            // 3. 生成钱包流水
+            // 4. 生成钱包流水
             WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
                     .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
                     .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());