guomengjiao 4 miesięcy temu
rodzic
commit
26c069a08a

+ 1 - 1
renren-admin/pom.xml

@@ -196,7 +196,7 @@
 		<dependency>
 			<groupId>com.github.wechatpay-apiv3</groupId>
 			<artifactId>wechatpay-java</artifactId>
-			<version>0.2.12</version>
+			<version>0.2.15</version>
 		</dependency>
     </dependencies>
 

+ 7 - 37
renren-admin/src/main/java/io/renren/modules/qmgj/wxpayutil/NewWxPayUtil.java

@@ -1,6 +1,5 @@
 package io.renren.modules.qmgj.wxpayutil;
 
-import cn.hutool.json.JSONUtil;
 import com.wechat.pay.java.core.RSAAutoCertificateConfig;
 import com.wechat.pay.java.core.notification.NotificationParser;
 import com.wechat.pay.java.core.notification.RequestParam;
@@ -8,6 +7,7 @@ import com.wechat.pay.java.service.transferbatch.TransferBatchService;
 import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
 import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse;
 import io.renren.common.exception.RRException;
+import io.renren.common.utils.config.PropertiesParameter;
 import io.renren.modules.wechat.config.NewWxPayProperties;
 import io.renren.modules.wechat.entity.TransferNotification;
 import lombok.extern.slf4j.Slf4j;
@@ -37,7 +37,7 @@ public class NewWxPayUtil {
      * @throws Exception
      */
     public TransferNotification transferNotify(HttpServletRequest request) throws Exception {
-        log.info("进入退款回调");
+        log.info("进入转账回调");
         //读取请求体的信息
         ServletInputStream inputStream = request.getInputStream();
         StringBuffer stringBuffer = new StringBuffer();
@@ -64,8 +64,7 @@ public class NewWxPayUtil {
                 .build();
 
         NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
-        TransferNotification parse = parser.parse(requestParam, TransferNotification.class);
-        return parse;
+        return parser.parse(requestParam, TransferNotification.class);
     }
 
 
@@ -82,42 +81,13 @@ public class NewWxPayUtil {
         if (StringUtils.isBlank(request.getTransferSceneId())) {
             request.setTransferSceneId("1000");
         }
-//        if(StringUtils.isBlank(request.getTransferRemark()))
-//        {
-//            request.setTransferRemark("转账");
-//        }
-//        if(StringUtils.isBlank(request.getNotifyUrl()))
-//        {
-//            request.setNotifyUrl(newWxPayProperties.getTransferNotifyUrl());
-//        }
+        if (StringUtils.isBlank(request.getNotifyUrl())) {
+            request.setNotifyUrl(PropertiesParameter.PROP.getProperty("pay.notify.transfer.url"));
+        }
         TransferBatchService service = new TransferBatchService.Builder().config(rsaAutoCertificateConfig).build();
         try {
-            InitiateBatchTransferResponse initiateBatchTransferResponse = service.initiateBatchTransfer(request);
-            log.error("转账信息,错误:{}", JSONUtil.toJsonStr(initiateBatchTransferResponse));
-//            TransferErrorCode transferErrorCode = TransferErrorCode.fromName(initiateSingleTransferResponse.getFailReason());
-//            TransferStatus transferStatus = TransferStatus.fromName(initiateSingleTransferResponse.getState());
-//            if (TransferStatus.SUCCESS.equals(transferStatus) ||
-//                TransferStatus.ACCEPTED.equals(transferStatus) ||
-//                TransferStatus.WAIT_USER_CONFIRM.equals(transferStatus) ||
-//                TransferStatus.TRANSFERING.equals(transferStatus)
-//            ){
-//                initiateSingleTransferResponse.setAppId(newWxPayProperties.getAppId());
-//                initiateSingleTransferResponse.setMchId(newWxPayProperties.getMchId());
-//                return initiateSingleTransferResponse;
-//            }
-//            else {
-//                if (ObjectUtil.isNotNull(transferErrorCode)) {
-//                    log.error("转账失败{},错误接款{}",transferErrorCode.name(),transferErrorCode.getDescription());
-//                    throw new ServiceException(transferErrorCode.getDescription());
-//                }
-//
-//            }
-            throw new RRException("转账失败");
-        } catch (com.wechat.pay.java.core.exception.ServiceException e) {
-            log.error("微信转账失败", e);
-            throw new RRException(String.format("微信转账失败,错识码:%s,描述:%s", e.getErrorCode(), e.getErrorMessage()));
+            return service.initiateBatchTransfer(request);
         } catch (Exception e) {
-            e.printStackTrace();
             log.error("转账失败,错误{}", e.getMessage(), e);
             throw new RRException(e.getMessage());
         }

+ 7 - 0
renren-admin/src/main/java/io/renren/modules/qyh/service/MemberWalletService.java

@@ -40,4 +40,11 @@ public interface MemberWalletService extends IService<MemberWalletEntity> {
      * @param money
      */
     void unfreezeAndRefund(Long memberId, BigDecimal money);
+
+    /**
+     * 扣除钱包余额
+     * @param memberId
+     * @param money
+     */
+    void deductBalance(Long memberId, BigDecimal money);
 }

+ 21 - 0
renren-admin/src/main/java/io/renren/modules/qyh/service/impl/MemberWalletServiceImpl.java

@@ -125,4 +125,25 @@ public class MemberWalletServiceImpl extends ServiceImpl<MemberWalletMapper, Mem
         wallet.setUpdateTime(LocalDateTime.now());
         baseMapper.updateById(wallet);
     }
+
+    @Override
+    public void deductBalance(Long memberId, BigDecimal money) {
+        MemberWalletEntity wallet = this.getOrCreateWallet(memberId);
+        // 校验待结算金额是否足够
+        if (wallet.getPendingAmount().compareTo(money) < 0) {
+            throw new RRException("待结算金额不足");
+        }
+        // 减少待结算金额
+        wallet.setPendingAmount(wallet.getPendingAmount().subtract(money));
+        wallet.setUpdateTime(LocalDateTime.now());
+        baseMapper.updateById(wallet);
+        // 记录佣金流水(提现支出)
+        CommissionRecordEntity record = new CommissionRecordEntity();
+        record.setMemberId(memberId);
+        record.setType(CommissionRecordTypeEnum.COMMISSION_WITHDRAW.value());
+        record.setAmount(money.negate()); // 负数表示支出
+        record.setRemark("提现扣款");
+        record.setCreateTime(LocalDateTime.now());
+        commissionRecordService.save(record);
+    }
 }

+ 76 - 1
renren-admin/src/main/java/io/renren/modules/qyh/service/impl/WithdrawInfoServiceImpl.java

@@ -2,10 +2,14 @@ package io.renren.modules.qyh.service.impl;
 
 import cn.hutool.core.lang.Snowflake;
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
+import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse;
+import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput;
 import io.renren.common.enums.WithdrawAuditStateEnum;
 import io.renren.common.enums.WithdrawTypeEnum;
 import io.renren.common.exception.RRException;
@@ -14,6 +18,7 @@ import io.renren.common.utils.LocalDateTimeUtils;
 import io.renren.common.utils.PageUtils;
 import io.renren.modules.qmgj.entity.MemberInfoEntity;
 import io.renren.modules.qmgj.service.MemberInfoService;
+import io.renren.modules.qmgj.wxpayutil.NewWxPayUtil;
 import io.renren.modules.qmjz.utils.BeanCopyUtils;
 import io.renren.modules.qyh.entity.MemberWalletEntity;
 import io.renren.modules.qyh.entity.WithdrawConfigEntity;
@@ -24,12 +29,16 @@ import io.renren.modules.qyh.model.vo.WithdrawInfoVO;
 import io.renren.modules.qyh.service.MemberWalletService;
 import io.renren.modules.qyh.service.WithdrawConfigService;
 import io.renren.modules.qyh.service.WithdrawInfoService;
+import io.renren.modules.wechat.enums.BatchStatusEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -39,6 +48,7 @@ import java.util.Map;
  * @author qingyunhui
  * @date 2026-04-24
  */
+@Slf4j
 @Service("withdrawInfoService")
 public class WithdrawInfoServiceImpl extends ServiceImpl<WithdrawInfoMapper, WithdrawInfoEntity>
         implements WithdrawInfoService {
@@ -52,6 +62,9 @@ public class WithdrawInfoServiceImpl extends ServiceImpl<WithdrawInfoMapper, Wit
     @Autowired
     private MemberInfoService memberInfoService;
 
+    @Autowired
+    private NewWxPayUtil wxPayUtil;
+
     @Override
     public PageUtils queryPage(Map<String, Object> params) {
         Integer page = MapUtil.getInt(params, Constant.PAGE);
@@ -129,7 +142,8 @@ public class WithdrawInfoServiceImpl extends ServiceImpl<WithdrawInfoMapper, Wit
         entity.setAuditTime(LocalDateTime.now());
         // 审核通过
         if (auditState.equals(WithdrawAuditStateEnum.AUDIT_PASS.value())) {
-            // TODO: 这里调用微信接口进行打款
+            // 这里调用微信接口进行打款
+            wxTransfer(entity);
         } else if (auditState.equals(WithdrawAuditStateEnum.AUDIT_REFUSE.value())) {
             // 拒绝后需要解冻并退回钱包余额
             memberWalletService.unfreezeAndRefund(entity.getMemberId(), entity.getMoney());
@@ -139,6 +153,67 @@ public class WithdrawInfoServiceImpl extends ServiceImpl<WithdrawInfoMapper, Wit
         baseMapper.updateById(entity);
     }
 
+    private void wxTransfer(WithdrawInfoEntity entity) {
+        // 1. 获取会员信息(需要 openid)
+        MemberInfoEntity memberInfo = memberInfoService.getById(entity.getMemberId());
+        if (memberInfo == null || StringUtils.isBlank(memberInfo.getOpenid())) {
+            throw new RRException("会员信息不存在或未绑定微信");
+        }
+        // 2. 构建转账请求
+        InitiateBatchTransferRequest request = new InitiateBatchTransferRequest();
+        // 商户系统内部的批次单号(使用提现记录的业务单号)
+        request.setOutBatchNo(entity.getBusinessCode());
+        String remark = "提现" + entity.getMoney() + "元";
+        // 批次名称
+        request.setBatchName(remark);
+        // 批次备注
+        request.setBatchRemark(remark);
+        // 转账总金额(单位:分)
+        BigDecimal moneyInFen = entity.getMoney().multiply(new BigDecimal("100"));
+        request.setTotalAmount(moneyInFen.longValue());
+        // 转账总笔数
+        request.setTotalNum(1);
+        // 转账明细列表
+        List<TransferDetailInput> detailList = new ArrayList<>();
+        TransferDetailInput detail = new TransferDetailInput();
+        // 商户系统内部的明细单号
+        detail.setOutDetailNo(entity.getBusinessCode() + "_01");
+        // 转账金额(单位:分)
+        detail.setTransferAmount(moneyInFen.longValue());
+        // 转账备注
+        detail.setTransferRemark(remark);
+        // 收款用户openid
+        detail.setOpenid(memberInfo.getOpenid());
+        detailList.add(detail);
+        request.setTransferDetailList(detailList);
+        log.info("发起微信转账,提现单号:{},金额:{}元,openid:{}",
+                entity.getBusinessCode(), entity.getMoney(), memberInfo.getOpenid());
+        // 3. 调用微信转账接口
+        InitiateBatchTransferResponse response = wxPayUtil.transferSingle(request);
+        log.info("微信转账响应:{}", JSONUtil.toJsonStr(response));
+        // 4. 处理转账结果
+        if (response != null) {
+            String batchStatus = response.getBatchStatus();
+            // FINISHED(已完成)表示成功
+            if (BatchStatusEnum.FINISHED.value().equals(batchStatus)) {
+                // 保存微信批次单号
+                entity.setThirdTransactionNo(response.getBatchId());
+                entity.setRemitTime(LocalDateTime.now());
+                entity.setAuditState(WithdrawAuditStateEnum.REMIT_SUCCESS.value());
+                //扣余额
+                memberWalletService.deductBalance(entity.getMemberId(), entity.getMoney());
+            } else if (BatchStatusEnum.CLOSED.value().equals(batchStatus)) {
+                // 转账失败,退回余额
+                memberWalletService.unfreezeAndRefund(entity.getMemberId(), entity.getMoney());
+                entity.setAuditState(WithdrawAuditStateEnum.AUDIT_REFUSE.value());
+                entity.setAuditExplain("打款失败:" + batchStatus);
+                entity.setThirdTransactionNo(response.getBatchId());
+            }
+        } else {
+            throw new RRException("微信转账失败");
+        }
+    }
+
     @Override
     public Map<String, BigDecimal> getWithdrawStatistics(Map<String, Object> params) {
         return baseMapper.getWithdrawStatistics(params);

+ 70 - 80
renren-admin/src/main/java/io/renren/modules/wechat/entity/TransferNotification.java

@@ -6,88 +6,79 @@ import com.google.gson.annotations.SerializedName;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class TransferNotification {
-    @SerializedName("out_bill_no")
-    private String outBillNo;
-    @SerializedName("transfer_bill_no")
-    private String transferBillNo;
-    @SerializedName("state")
-    private String state;
-
-    @SerializedName("mch_id")
-    private String mchId;
-    @SerializedName("transfer_amount")
-    private Long transferAmount;
-    @SerializedName("openid")
-    private String openid;
-    @SerializedName("fail_reason")
-    private String failReason;
-    @SerializedName("create_time")
-    private String createTime;
+    @SerializedName("out_batch_no")
+    private String outBatchNo;
+    @SerializedName("batch_id")
+    private String batchId;
+    @SerializedName("batch_status")
+    private String batchStatus;
+    @SerializedName("total_num")
+    private Integer totalNum;
+    @SerializedName("total_amount")
+    private Integer totalAmount;
+    @SerializedName("success_amount")
+    private Integer successAmount;
+    @SerializedName("success_num")
+    private Integer successNum;
     @SerializedName("update_time")
     private String updateTime;
+    @SerializedName("close_reason")
+    private String closeReason;
 
-    public String getOutBillNo() {
-        return outBillNo;
+    public String getOutBatchNo() {
+        return outBatchNo;
     }
 
-    public void setOutBillNo(String outBillNo) {
-        this.outBillNo = outBillNo;
+    public void setOutBatchNo(String outBatchNo) {
+        this.outBatchNo = outBatchNo;
     }
 
-    public String getTransferBillNo() {
-        return transferBillNo;
+    public String getBatchId() {
+        return batchId;
     }
 
-    public void setTransferBillNo(String transferBillNo) {
-        this.transferBillNo = transferBillNo;
+    public void setBatchId(String batchId) {
+        this.batchId = batchId;
     }
 
-    public String getState() {
-        return state;
+    public String getBatchStatus() {
+        return batchStatus;
     }
 
-    public void setState(String state) {
-        this.state = state;
+    public void setBatchStatus(String batchStatus) {
+        this.batchStatus = batchStatus;
     }
 
-    public String getMchId() {
-        return mchId;
+    public Integer getTotalNum() {
+        return totalNum;
     }
 
-    public void setMchId(String mchId) {
-        this.mchId = mchId;
+    public void setTotalNum(Integer totalNum) {
+        this.totalNum = totalNum;
     }
 
-    public Long getTransferAmount() {
-        return transferAmount;
+    public Integer getTotalAmount() {
+        return totalAmount;
     }
 
-    public void setTransferAmount(Long transferAmount) {
-        this.transferAmount = transferAmount;
+    public void setTotalAmount(Integer totalAmount) {
+        this.totalAmount = totalAmount;
     }
 
-    public String getOpenid() {
-        return openid;
+    public Integer getSuccessAmount() {
+        return successAmount;
     }
 
-    public void setOpenid(String openid) {
-        this.openid = openid;
+    public void setSuccessAmount(Integer successAmount) {
+        this.successAmount = successAmount;
     }
 
-    public String getFailReason() {
-        return failReason;
+    public Integer getSuccessNum() {
+        return successNum;
     }
 
-    public void setFailReason(String failReason) {
-        this.failReason = failReason;
-    }
-
-    public String getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(String createTime) {
-        this.createTime = createTime;
+    public void setSuccessNum(Integer successNum) {
+        this.successNum = successNum;
     }
 
     public String getUpdateTime() {
@@ -98,54 +89,53 @@ public class TransferNotification {
         this.updateTime = updateTime;
     }
 
+    public String getCloseReason() {
+        return closeReason;
+    }
+
+    public void setCloseReason(String closeReason) {
+        this.closeReason = closeReason;
+    }
+
     public static class Builder {
         private TransferNotification notification = new TransferNotification();
 
-        public Builder outBillNo(String outBillNo) {
-            notification.outBillNo = outBillNo;
+        public Builder outBatchNo(String outBatchNo) {
+            notification.outBatchNo = outBatchNo;
             return this;
         }
-
-        public Builder transferBillNo(String transferBillNo) {
-            notification.transferBillNo = transferBillNo;
+        public Builder batchId(String batchId) {
+            notification.batchId = batchId;
             return this;
         }
-
-        public Builder state(String state) {
-            notification.state = state;
+        public Builder batchStatus(String batchStatus) {
+            notification.batchStatus = batchStatus;
             return this;
         }
-
-        public Builder mchId(String mchId) {
-            notification.mchId = mchId;
+        public Builder totalNum(Integer totalNum) {
+            notification.totalNum = totalNum;
             return this;
         }
-
-        public Builder transferAmount(Long transferAmount) {
-            notification.transferAmount = transferAmount;
+        public Builder totalAmount(Integer totalAmount) {
+            notification.totalAmount = totalAmount;
             return this;
         }
-
-        public Builder openid(String openid) {
-            notification.openid = openid;
-            return this;
-        }
-
-        public Builder failReason(String failReason) {
-            notification.failReason = failReason;
+        public Builder successAmount(Integer successAmount) {
+            notification.successAmount = successAmount;
             return this;
         }
-
-        public Builder createTime(String createTime) {
-            notification.createTime = createTime;
+        public Builder successNum(Integer successNum) {
+            notification.successNum = successNum;
             return this;
         }
-
         public Builder updateTime(String updateTime) {
             notification.updateTime = updateTime;
             return this;
         }
-
+        public Builder closeReason(String closeReason) {
+            notification.closeReason = closeReason;
+            return this;
+        }
         public TransferNotification build() {
             return notification;
         }

+ 48 - 0
renren-admin/src/main/java/io/renren/modules/wechat/enums/BatchStatusEnum.java

@@ -0,0 +1,48 @@
+package io.renren.modules.wechat.enums;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+/**
+ *     WAIT_PAY: 待付款确认。需要付款出资商户在商家助手小程序或服务商助手小程序进行付款确认
+ *     ACCEPTED:已受理。批次已受理成功,若发起批量转账的30分钟后,转账批次单仍处于该状态,可能原因是商户账户余额不足等。商户可查询账户资金流水,若该笔转账批次单的扣款已经发生,则表示批次已经进入转账中,请再次查单确认
+ *     PROCESSING:转账中。已开始处理批次内的转账明细单
+ *     FINISHED:已完成。批次内的所有转账明细单都已处理完成
+ *     CLOSED:已关闭。可查询具体的批次关闭原因确认
+ *
+ * @author lijiahe
+ */
+public enum BatchStatusEnum {
+    WAIT_PAY("WAIT_PAY", "待付款确认"),
+    ACCEPTED("ACCEPTED", "已受理"),
+    PROCESSING("PROCESSING", "转账中"),
+    FINISHED("FINISHED", "已完成"),
+    CLOSED("CLOSED", "已关闭"),
+    ;
+
+    private String value;
+    private String desc;
+
+    BatchStatusEnum(String value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    public String value() {
+        return this.value;
+    }
+
+    public String desc() {
+        return this.desc;
+    }
+
+    @JsonCreator
+    public static BatchStatusEnum getByCode(String value) {
+        for (BatchStatusEnum ynEnum : BatchStatusEnum.values()) {
+            if (ObjectUtil.equal(value, ynEnum.value())) {
+                return ynEnum;
+            }
+        }
+        return null;
+    }
+}

+ 1 - 0
renren-admin/src/main/resources/pay.properties

@@ -14,6 +14,7 @@ pay.notify.meet.url=https://qingyunhui.songlanyun.com/qyh/api/order/meetPayCallb
 ## 活动支付回调地址
 pay.notify.activity.url=https://qingyunhui.songlanyun.com/qyh/api/act/register/wx/back
 pay.notify.enterprise.url=https://qingyunhui.songlanyun.com/qyh/api/enterprise/enterprisePaysCallback
+pay.notify.transfer.url=https://frp.songlanyun.com/qyh/api/transfer/notify
 
 
 # 本地 退款P12文件目录