Gqingci 12 часов назад
Родитель
Сommit
a178e92818

+ 10 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalWithdrawController.java

@@ -139,6 +139,16 @@ public class PortalWithdrawController extends BaseController {
         return withdrawService.handleAlipayTransferNotify(params);
     }
 
+    /**
+     * 微信转账回调通知
+     * 注意:由于微信没有沙盒环境,此接口暂时注释掉实际逻辑
+     */
+    // @SaIgnore
+    // @PostMapping("/wechat/transfer/notify")
+    // public String wechatTransferNotify(@RequestBody String requestBody) {
+    //     return withdrawService.handleWechatTransferNotify(requestBody);
+    // }
+
     private SysTenantVo getCurrentCompany() {
         String tenantId = LoginHelper.getTenantId();
         if (tenantId == null || tenantId.isBlank()) {

+ 6 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentConfigService.java

@@ -14,6 +14,12 @@ public interface IPaymentConfigService {
 
     PaymentConfig getEnabledWechatConfig();
 
+    /**
+     * 获取启用的微信付款(转账/提现)配置
+     * config_type=2(付款),payment_type=2(微信)
+     */
+    PaymentConfig getEnabledWechatTransferConfig();
+
     PaymentConfigVo getWechatPayConfig();
 
     void saveWechatPayConfig(PaymentConfigBo bo);

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IWithdrawService.java

@@ -23,4 +23,9 @@ public interface IWithdrawService {
     Boolean auditReject(Long id, String auditRemark);
 
     String handleAlipayTransferNotify(Map<String, String> params);
+
+    /**
+     * 处理微信转账回调通知
+     */
+    String handleWechatTransferNotify(String requestBody);
 }

+ 12 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentConfigServiceImpl.java

@@ -57,6 +57,18 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
         );
     }
 
+    @Override
+    public PaymentConfig getEnabledWechatTransferConfig() {
+        return paymentConfigMapper.selectOne(
+            Wrappers.<PaymentConfig>lambdaQuery()
+                .eq(PaymentConfig::getConfigType, 2)
+                .eq(PaymentConfig::getPaymentType, 2)
+                .eq(PaymentConfig::getIsEnabled, 1)
+                .orderByDesc(PaymentConfig::getCreateTime)
+                .last("limit 1")
+        );
+    }
+
     @Override
     public PaymentConfigVo getWechatPayConfig() {
         PaymentConfig config = getOrCreateWechatConfig();

+ 132 - 2
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/WithdrawServiceImpl.java

@@ -152,6 +152,12 @@ public class WithdrawServiceImpl implements IWithdrawService {
             throw new ServiceException("提现状态不正确,当前状态:{}", withdraw.getWithdrawStatus());
         }
 
+        // 查询收款账户,判断提现方式
+        WithdrawAccount account = withdrawAccountMapper.selectById(withdraw.getAccountId());
+        if (account == null) {
+            throw new ServiceException("收款账户不存在");
+        }
+
         withdraw.setWithdrawStatus(1);
         withdraw.setAuditorId(LoginHelper.getUserId());
         withdraw.setAuditTime(new Date());
@@ -159,7 +165,14 @@ public class WithdrawServiceImpl implements IWithdrawService {
         withdrawMapper.updateById(withdraw);
 
         try {
-            String tradeNo = transferToAlipay(withdraw);
+            String tradeNo;
+            if (Objects.equals(account.getAccountType(), 2)) {
+                // 微信提现
+                tradeNo = transferToWechat(withdraw);
+            } else {
+                // 支付宝提现(默认)
+                tradeNo = transferToAlipay(withdraw);
+            }
 
             SysTenant tenant = sysTenantMapper.selectById(withdraw.getCompanyId());
             if (tenant == null) {
@@ -174,8 +187,9 @@ public class WithdrawServiceImpl implements IWithdrawService {
             update.setWithdrawingBalance(withdrawingBalance.subtract(withdraw.getWithdrawAmount()));
             sysTenantMapper.updateById(update);
 
+            String channelName = Objects.equals(account.getAccountType(), 2) ? "微信" : "支付宝";
             insertFlow(tenant.getId(), 2, withdraw.getWithdrawAmount(), 3, withdrawingBalance,
-                withdrawingBalance.subtract(withdraw.getWithdrawAmount()), 6, withdraw.getId(), withdraw.getWithdrawNo(), "提现成功:" + withdraw.getWithdrawNo());
+                withdrawingBalance.subtract(withdraw.getWithdrawAmount()), 6, withdraw.getId(), withdraw.getWithdrawNo(), channelName + "提现成功:" + withdraw.getWithdrawNo());
 
             withdraw.setWithdrawStatus(3);
             withdraw.setTradeNo(tradeNo);
@@ -456,6 +470,122 @@ public class WithdrawServiceImpl implements IWithdrawService {
         throw new ServiceException("支付宝返回错误:{}", response.getSubMsg());
     }
 
+    /**
+     * 微信商家转账到零钱
+     * 注意:由于微信没有沙盒环境,实际API调用已注释,仅做数据库操作测试
+     */
+    private String transferToWechat(Withdraw withdraw) throws Exception {
+        // ---------- 以下为实际微信转账逻辑,暂时注释掉 ----------
+        /*
+        PaymentConfig config = paymentConfigService.getEnabledWechatTransferConfig();
+        if (config == null) {
+            throw new ServiceException("未找到启用的微信付款配置,请先配置微信付款参数");
+        }
+
+        WithdrawAccount account = withdrawAccountMapper.selectById(withdraw.getAccountId());
+        if (account == null) {
+            throw new ServiceException("收款账户不存在");
+        }
+
+        // 使用 wechatpay-java SDK 发起商家转账
+        // 1. 构建配置
+        RSAAutoCertificateConfig wxConfig = new RSAAutoCertificateConfig.Builder()
+            .merchantId(config.getMchId())
+            .privateKeyFromPath(config.getPrivateKeyPath())
+            .merchantSerialNumber(config.getSerialNo())
+            .apiV3Key(config.getApiV3Key())
+            .build();
+
+        // 2. 构建转账请求
+        com.wechat.pay.java.service.transferbatch.TransferBatchService transferService =
+            new com.wechat.pay.java.service.transferbatch.TransferBatchService.Builder().config(wxConfig).build();
+
+        com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest request =
+            new com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest();
+        request.setAppid(config.getAppId());
+        request.setOutBatchNo(withdraw.getWithdrawNo());
+        request.setBatchName("平台背调佣金提现");
+        request.setBatchRemark("提现单号:" + withdraw.getWithdrawNo());
+        request.setTotalAmount(withdraw.getWithdrawAmount().multiply(new java.math.BigDecimal(100)).longValue());
+        request.setTotalNum(1);
+
+        com.wechat.pay.java.service.transferbatch.model.TransferDetailInput detail =
+            new com.wechat.pay.java.service.transferbatch.model.TransferDetailInput();
+        detail.setOutDetailNo(withdraw.getWithdrawNo() + "D1");
+        detail.setTransferAmount(withdraw.getWithdrawAmount().multiply(new java.math.BigDecimal(100)).longValue());
+        detail.setTransferRemark("佣金提现");
+        detail.setOpenid(account.getAccountNumber()); // 微信账户存的是openid
+        request.setTransferDetailList(java.util.List.of(detail));
+
+        // 3. 发起转账
+        com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse response =
+            transferService.initiateBatchTransfer(request);
+        return response.getBatchId();
+        */
+        // ---------- 以上为实际微信转账逻辑,暂时注释掉 ----------
+
+        // 测试模式:跳过实际微信打款,直接返回模拟交易号
+        return "WX_TEST_" + System.currentTimeMillis();
+    }
+
+    /**
+     * 处理微信转账回调通知
+     * 注意:由于微信没有沙盒环境,此方法暂时注释核心逻辑
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String handleWechatTransferNotify(String requestBody) {
+        // ---------- 以下为实际微信回调处理逻辑,暂时注释掉 ----------
+        /*
+        try {
+            PaymentConfig config = paymentConfigService.getEnabledWechatTransferConfig();
+            if (config == null) {
+                return "{\"code\":\"FAIL\",\"message\":\"未找到微信付款配置\"}";
+            }
+
+            // 解密回调数据中的 resource 字段,获取转账详情
+            // 根据 out_batch_no 查找提现记录
+            // 根据转账状态更新提现记录
+            // SUCCESS -> 提现成功(状态3)
+            // FAIL -> 提现失败(状态4),退回余额
+
+            JSONObject notify = JSONUtil.parseObj(requestBody);
+            String outBatchNo = notify.getStr("out_batch_no");
+            String batchStatus = notify.getStr("batch_status");
+
+            Withdraw withdraw = withdrawMapper.selectOne(Wrappers.<Withdraw>lambdaQuery()
+                .eq(Withdraw::getWithdrawNo, outBatchNo)
+                .last("limit 1"));
+            if (withdraw == null) {
+                return "{\"code\":\"FAIL\",\"message\":\"提现记录不存在\"}";
+            }
+            if (Objects.equals(withdraw.getWithdrawStatus(), 3)) {
+                return "{\"code\":\"SUCCESS\",\"message\":\"已处理\"}";
+            }
+
+            if ("FINISHED".equals(batchStatus)) {
+                withdraw.setTradeNo(notify.getStr("batch_id"));
+                withdraw.setWithdrawStatus(3);
+                withdraw.setTransferTime(new Date());
+                withdrawMapper.updateById(withdraw);
+            } else if ("CLOSED".equals(batchStatus)) {
+                refundWithdrawToAvailable(withdraw, "微信转账失败,退回可使用余额");
+                withdraw.setWithdrawStatus(4);
+                withdraw.setFailReason("微信转账批次关闭");
+                withdrawMapper.updateById(withdraw);
+            }
+
+            return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";
+        } catch (Exception e) {
+            return "{\"code\":\"FAIL\",\"message\":\"处理异常\"}";
+        }
+        */
+        // ---------- 以上为实际微信回调处理逻辑,暂时注释掉 ----------
+
+        // 测试模式:直接返回成功
+        return "{\"code\":\"SUCCESS\",\"message\":\"测试模式\"}";
+    }
+
     private void insertFlow(Long companyId, Integer flowType, BigDecimal amount, Integer balanceType,
                             BigDecimal balanceBefore, BigDecimal balanceAfter, Integer businessType,
                             Long businessId, String businessNo, String remark) {