浏览代码

Merge branch 'yic' of http://115.29.66.169:10080/qingyunhui/java into yic

chengjunhui 4 月之前
父节点
当前提交
e32534fae2

+ 5 - 0
renren-admin/pom.xml

@@ -193,6 +193,11 @@
             <version>1.1.0</version>
             <scope>compile</scope>
         </dependency>
+		<dependency>
+			<groupId>com.github.wechatpay-apiv3</groupId>
+			<artifactId>wechatpay-java</artifactId>
+			<version>0.2.15</version>
+		</dependency>
     </dependencies>
 
 	<build>

+ 97 - 0
renren-admin/src/main/java/io/renren/modules/qmgj/wxpayutil/NewWxPayUtil.java

@@ -0,0 +1,97 @@
+package io.renren.modules.qmgj.wxpayutil;
+
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.notification.NotificationParser;
+import com.wechat.pay.java.core.notification.RequestParam;
+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;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+@Slf4j
+@Component
+public class NewWxPayUtil {
+
+    @Resource(name = "rsaAutoCertificateConfig")
+    private RSAAutoCertificateConfig rsaAutoCertificateConfig;
+    @Resource
+    private NewWxPayProperties newWxPayProperties;
+
+    /**
+     * 转账回调
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public TransferNotification transferNotify(HttpServletRequest request) throws Exception {
+        log.info("进入转账回调");
+        //读取请求体的信息
+        ServletInputStream inputStream = request.getInputStream();
+        StringBuffer stringBuffer = new StringBuffer();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+        String s;
+        //读取回调请求体
+        while ((s = bufferedReader.readLine()) != null) {
+            stringBuffer.append(s);
+        }
+        String s1 = stringBuffer.toString();
+        String timestamp = request.getHeader(NewWxPayProperties.WECHAT_PAY_TIMESTAMP);
+        String nonce = request.getHeader(NewWxPayProperties.WECHAT_PAY_NONCE);
+        String signType = request.getHeader("Wechatpay-Signature-Type");
+        String serialNo = request.getHeader(NewWxPayProperties.WECHAT_PAY_SERIAL);
+        String signature = request.getHeader(NewWxPayProperties.WECHAT_PAY_SIGNATURE);
+
+        RequestParam requestParam = new RequestParam.Builder()
+                .serialNumber(serialNo)
+                .nonce(nonce)
+                .signature(signature)
+                .timestamp(timestamp)
+                .signType(signType)
+                .body(s1)
+                .build();
+
+        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
+        return parser.parse(requestParam, TransferNotification.class);
+    }
+
+
+    /**
+     * 转账到零钱
+     *
+     * @param request
+     * @return
+     */
+    public InitiateBatchTransferResponse transferSingle(InitiateBatchTransferRequest request) {
+        if (StringUtils.isBlank(request.getAppid())) {
+            request.setAppid(newWxPayProperties.getAppId());
+        }
+        if (StringUtils.isBlank(request.getTransferSceneId())) {
+            request.setTransferSceneId("1000");
+        }
+        if (StringUtils.isBlank(request.getNotifyUrl())) {
+            request.setNotifyUrl(PropertiesParameter.PROP.getProperty("pay.notify.transfer.url"));
+        }
+        TransferBatchService service = new TransferBatchService.Builder().config(rsaAutoCertificateConfig).build();
+        try {
+            return service.initiateBatchTransfer(request);
+        } catch (Exception e) {
+            log.error("转账失败,错误{}", e.getMessage(), e);
+            throw new RRException(e.getMessage());
+        }
+    }
+
+
+}

+ 51 - 0
renren-admin/src/main/java/io/renren/modules/qyh/api/ApiWithdrawController.java

@@ -1,19 +1,28 @@
 package io.renren.modules.qyh.api;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import io.renren.common.annotation.RepeatSubmit;
+import io.renren.common.enums.WithdrawAuditStateEnum;
+import io.renren.common.enums.WithdrawPaymentTermEnum;
 import io.renren.common.utils.Constant;
 import io.renren.common.utils.PageUtils;
 import io.renren.common.utils.R;
+import io.renren.modules.qmgj.wxpayutil.NewWxPayUtil;
 import io.renren.modules.qyh.entity.WithdrawInfoEntity;
 import io.renren.modules.qyh.model.vo.MyWalletVO;
 import io.renren.modules.qyh.service.MemberWalletService;
 import io.renren.modules.qyh.service.WithdrawInfoService;
+import io.renren.modules.wechat.entity.TransferNotification;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.util.Map;
 
 /**
@@ -30,6 +39,9 @@ public class ApiWithdrawController {
     @Autowired
     private MemberWalletService memberWalletService;
 
+    @Autowired
+    private NewWxPayUtil wxPayUtil;
+
     /**
      * 我的钱包
      */
@@ -61,4 +73,43 @@ public class ApiWithdrawController {
         return R.ok().put("page", page);
     }
 
+    /**
+     * 转账回调
+     */
+    @PostMapping(value = "transferCallback", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    @RepeatSubmit()
+    public String transferCallback(HttpServletRequest request) {
+        try {
+            // 1. 验签并解析通知
+            TransferNotification notification = wxPayUtil.transferNotify(request);
+            // 2. 根据业务单号查询提现记录
+            String outBatchNo = notification.getOutBatchNo();
+            WithdrawInfoEntity entity = withdrawInfoService.getOne(
+                    new LambdaQueryWrapper<WithdrawInfoEntity>()
+                            .eq(WithdrawInfoEntity::getBusinessCode, outBatchNo));
+            if (entity == null) {
+                return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";
+            }
+            if (!entity.getPaymentTerm().equals(WithdrawPaymentTermEnum.WECHAT.value())) {
+                return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";
+            }
+            // 3. 状态校验
+            if (entity.getAuditState().equals(WithdrawAuditStateEnum.REMIT_SUCCESS.value()) ||
+                    entity.getAuditState().equals(WithdrawAuditStateEnum.AUDIT_REFUSE.value())) {
+                return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}"; // 已处理过,直接返回成功
+            }
+            // 微信回调返回的金额单位是分,需要转换为元后比较
+            BigDecimal successAmountYuan = new BigDecimal(notification.getSuccessAmount())
+                    .divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP);
+            if (entity.getMoney().compareTo(successAmountYuan) != 0) {
+                return "{\"code\":\"FAIL\",\"message\":\"金额不一致\"}";
+            }
+            // 处理提现状态
+            withdrawInfoService.handleTransferStatus(entity, notification.getBatchStatus(), notification.getBatchId());
+            return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";
+        } catch (Exception e) {
+            return "{\"code\":\"FAIL\",\"message\":\"处理失败\"}";
+        }
+    }
 }

+ 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);
 }

+ 5 - 0
renren-admin/src/main/java/io/renren/modules/qyh/service/WithdrawInfoService.java

@@ -34,4 +34,9 @@ public interface WithdrawInfoService extends IService<WithdrawInfoEntity> {
     Map<String, BigDecimal> getWithdrawStatistics(Map<String, Object> params);
 
     WithdrawInfoVO info(Long id);
+
+    /**
+     * 处理转账状态
+     */
+    void handleTransferStatus(WithdrawInfoEntity entity, String batchStatus, String batchId);
 }

+ 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);
+    }
 }

+ 85 - 2
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);
@@ -127,18 +140,67 @@ public class WithdrawInfoServiceImpl extends ServiceImpl<WithdrawInfoMapper, Wit
         entity.setAuditExplain(dto.getAuditExplain());
         entity.setAuditPer(dto.getAuditPer());
         entity.setAuditTime(LocalDateTime.now());
+        baseMapper.updateById(entity);
         // 审核通过
         if (auditState.equals(WithdrawAuditStateEnum.AUDIT_PASS.value())) {
-            // TODO: 这里调用微信接口进行打款
+            // 这里调用微信接口进行打款
+            wxTransfer(entity);
         } else if (auditState.equals(WithdrawAuditStateEnum.AUDIT_REFUSE.value())) {
             // 拒绝后需要解冻并退回钱包余额
             memberWalletService.unfreezeAndRefund(entity.getMemberId(), entity.getMoney());
         } else {
             throw new RRException("审核状态不正确");
         }
-        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) {
+            handleTransferStatus(entity, response.getBatchStatus(), response.getBatchId());
+        } else {
+            throw new RRException("微信转账失败");
+        }
+    }
+
+
+
     @Override
     public Map<String, BigDecimal> getWithdrawStatistics(Map<String, Object> params) {
         return baseMapper.getWithdrawStatistics(params);
@@ -161,4 +223,25 @@ public class WithdrawInfoServiceImpl extends ServiceImpl<WithdrawInfoMapper, Wit
         withdrawInfoVO.setAvailableAmount(myWallet.getAvailableAmount());
         return withdrawInfoVO;
     }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void handleTransferStatus(WithdrawInfoEntity entity, String batchStatus, String batchId) {
+        // FINISHED(已完成)表示成功
+        if (BatchStatusEnum.FINISHED.value().equals(batchStatus)) {
+            // 保存微信批次单号
+            entity.setThirdTransactionNo(batchId);
+            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(batchId);
+        }
+        baseMapper.updateById(entity);
+    }
 }

+ 28 - 0
renren-admin/src/main/java/io/renren/modules/wechat/config/NewWxPayConfig.java

@@ -0,0 +1,28 @@
+package io.renren.modules.wechat.config;
+
+import com.wechat.pay.java.core.Config;
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+
+@Configuration
+public class NewWxPayConfig {
+
+    @Resource
+    private NewWxPayProperties newWxPayProperties;
+
+    @Bean(name = "rsaAutoCertificateConfig")
+    @ConditionalOnBean(NewWxPayProperties.class)
+    public RSAAutoCertificateConfig rsaPublicKeyConfig() {
+        RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
+            .merchantId(newWxPayProperties.getMchId())
+            .privateKeyFromPath(newWxPayProperties.getPrivateKeyPath())
+            .merchantSerialNumber(newWxPayProperties.getCertSerialNo())
+            .apiV3Key(newWxPayProperties.getApiv3())
+            .build();
+        return config;
+    }
+}

+ 59 - 0
renren-admin/src/main/java/io/renren/modules/wechat/config/NewWxPayProperties.java

@@ -0,0 +1,59 @@
+package io.renren.modules.wechat.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "wx.pay")
+public class NewWxPayProperties
+{
+
+
+    public static final String WECHAT_PAY_TIMESTAMP = "Wechatpay-Timestamp";
+    public static final String WECHAT_PAY_NONCE = "Wechatpay-Nonce";
+    public static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial";
+    public static final String WECHAT_PAY_SIGNATURE = "Wechatpay-Signature";
+    /**
+     * 设置微信公众号或者小程序等的appid
+     */
+    private String appId;
+
+    /**
+     * 微信支付商户号
+     */
+    private String mchId;
+
+    /**
+     * 微信支付商户密钥
+     */
+    private String mchKey;
+
+    /**
+     * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
+     */
+    private String subAppId;
+
+    /**
+     * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
+     */
+    private String subMchId;
+
+    /**
+     * apiV3
+     */
+    private String apiv3;
+
+    /**
+     * 证书序列号
+     */
+    private String certSerialNo;
+
+    /**
+     * 私钥证书路径
+     */
+    private String privateKeyPath;
+
+
+}

+ 143 - 0
renren-admin/src/main/java/io/renren/modules/wechat/entity/TransferNotification.java

@@ -0,0 +1,143 @@
+package io.renren.modules.wechat.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.google.gson.annotations.SerializedName;
+
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TransferNotification {
+    @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 Long totalAmount;
+    @SerializedName("success_amount")
+    private Long successAmount;
+    @SerializedName("success_num")
+    private Integer successNum;
+    @SerializedName("update_time")
+    private String updateTime;
+    @SerializedName("close_reason")
+    private String closeReason;
+
+    public String getOutBatchNo() {
+        return outBatchNo;
+    }
+
+    public void setOutBatchNo(String outBatchNo) {
+        this.outBatchNo = outBatchNo;
+    }
+
+    public String getBatchId() {
+        return batchId;
+    }
+
+    public void setBatchId(String batchId) {
+        this.batchId = batchId;
+    }
+
+    public String getBatchStatus() {
+        return batchStatus;
+    }
+
+    public void setBatchStatus(String batchStatus) {
+        this.batchStatus = batchStatus;
+    }
+
+    public Integer getTotalNum() {
+        return totalNum;
+    }
+
+    public void setTotalNum(Integer totalNum) {
+        this.totalNum = totalNum;
+    }
+
+    public Long getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(Long totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public Long getSuccessAmount() {
+        return successAmount;
+    }
+
+    public void setSuccessAmount(Long successAmount) {
+        this.successAmount = successAmount;
+    }
+
+    public Integer getSuccessNum() {
+        return successNum;
+    }
+
+    public void setSuccessNum(Integer successNum) {
+        this.successNum = successNum;
+    }
+
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(String updateTime) {
+        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 outBatchNo(String outBatchNo) {
+            notification.outBatchNo = outBatchNo;
+            return this;
+        }
+        public Builder batchId(String batchId) {
+            notification.batchId = batchId;
+            return this;
+        }
+        public Builder batchStatus(String batchStatus) {
+            notification.batchStatus = batchStatus;
+            return this;
+        }
+        public Builder totalNum(Integer totalNum) {
+            notification.totalNum = totalNum;
+            return this;
+        }
+        public Builder totalAmount(Long totalAmount) {
+            notification.totalAmount = totalAmount;
+            return this;
+        }
+        public Builder successAmount(Long successAmount) {
+            notification.successAmount = successAmount;
+            return this;
+        }
+        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;
+    }
+}

+ 9 - 0
renren-admin/src/main/resources/application.yml

@@ -93,3 +93,12 @@ wx:
     # -- 订阅消息参数
     tokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={APPID}&secret={APPSECRET}
     sendUrl: https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=
+  pay:
+    appId: wx0ca047da2ecada85 #公司的:wxd15dbb580e6f1cd3  客户的:wx3f5fa98c5edf8718
+    mchId: 1703980824 #公司的:1640577480  客户的:1703980824
+    mchKey: #微信支付商户密钥
+    subAppId: #服务商模式下的子商户公众账号ID
+    subMchId: #服务商模式下的子商户号
+    apiv3: 5Bc8eR7zQ1X9gK3lMp2vN6sD4hJ0fAqW #公司的:7f279a5423a15118e9cb1fc381631f61   客户的:5Bc8eR7zQ1X9gK3lMp2vN6sD4hJ0fAqW
+    certSerialNo: 5C8AB18A9CC11514B1343E422C0C57D620B157DC #证书序列号公司的:5DDEE5F70C50743B6B3BFC92263696D2A68AC951,客户的:5C8AB18A9CC11514B1343E422C0C57D620B157DC
+    privateKeyPath: "ruoyi-admin/src/main/resources/cert/apiclient_key.pem"

+ 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/withdraw/transferCallback
 
 
 # 本地 退款P12文件目录