فهرست منبع

add withdraw api

Mr.qian 6 روز پیش
والد
کامیت
5744e78617

+ 73 - 0
cif-api/src/main/java/com/txz/cif/dto/tfpay/CheckWithdrawSignDTO.java

@@ -0,0 +1,73 @@
+package com.txz.cif.dto.tfpay;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/2
+ */
+@Data
+public class CheckWithdrawSignDTO {
+    
+    /**
+     * 商户编号 - 平台分配商户号
+     */
+    @JsonProperty("memberid")
+    private String memberid;
+    
+    /**
+     * 商户订单号 - 订单号唯一, 字符长度20
+     */
+    @JsonProperty("orderid")
+    private String orderid;
+    
+    /**
+     * 订单金额 - 订单金额 单位:元
+     */
+    @JsonProperty("amount")
+    private String amount;
+    
+    /**
+     * 平台订单号 - 平台订单号
+     */
+    @JsonProperty("transaction_id")
+    private String transaction_id;
+    
+    /**
+     * 用户姓名 - 用户姓名 如:张三
+     */
+    @JsonProperty("pay_username")
+    private String pay_username;
+    
+    /**
+     * 银行账号 - 银行账号 如:银行卡号码
+     */
+    @JsonProperty("pay_banknumber")
+    private String pay_banknumber;
+    
+    /**
+     * 银行名称 - 银行名称 如:工商银行,可为空
+     */
+    @JsonProperty("pay_bankname")
+    private String pay_bankname;
+    
+    /**
+     * 交易成功时间 - 时间格式:Y-m-d H:i:s
+     */
+    @JsonProperty("datetime")
+    private String datetime;
+    
+    /**
+     * 交易状态 - 1 为成功,-1失败,0处理中
+     */
+    @JsonProperty("returncode")
+    private String returncode;
+    
+    /**
+     * 订单描述 - 如:失败原因
+     */
+    @JsonProperty("remark")
+    private String remark;
+
+}

+ 58 - 49
cif-service/src/main/java/com/txz/cif/constants/MyConstants.java

@@ -4,53 +4,62 @@ package com.txz.cif.constants;
  * @author lxk
  */
 public class MyConstants {
-
-  /**
-   * 兼容微信请求
-   */
-  public static final String DEFAULT = "default";
-
-  /**
-   * 平台资金账户(资产类)id
-   */
-  public static final Long INNER_CAPITAL = 1L;
-
-  /**
-   * 主营收入账户(损益类)id
-   */
-  public static final Long INNER_INCOME = 3L;
-
-  /**
-   * 主营支出-退款(损益类)id
-   */
-  public static final Long INNER_REFUND = 6L;
-
-  /**
-   * 主营支出-充值返点(损益类)id
-   */
-  public static final Long INNER_REBATE = 7L;
-
-
-
-
-
-  /**
-   * 平台营销账户(负债类)id
-   */
-  public static final Long INNER_MARKETING = 2L;
-
-
-
-  /**
-   *  本年利润
-   */
-  public static final Long INNER_PROFIT = 5L;
-
-
-  /**
-   *  用户默认头像
-   */
-  public static final String ICON_USER = "/public/user.png";
-
-  public static final String TF_PAY_NOTIFY_LOCK = "TF_PAY_NOTIFY_LOCK:%s";
+    
+    /**
+     * 兼容微信请求
+     */
+    public static final String DEFAULT = "default";
+    
+    /**
+     * 平台资金账户(资产类)id
+     */
+    public static final Long INNER_CAPITAL = 1L;
+    
+    /**
+     * 主营收入账户(损益类)id
+     */
+    public static final Long INNER_INCOME = 3L;
+    
+    /**
+     * 主营支出-退款(损益类)id
+     */
+    public static final Long INNER_REFUND = 6L;
+    
+    /**
+     * 主营支出-充值返点(损益类)id
+     */
+    public static final Long INNER_REBATE = 7L;
+    
+    
+    /**
+     * 平台营销账户(负债类)id
+     */
+    public static final Long INNER_MARKETING = 2L;
+    
+    
+    /**
+     * 本年利润
+     */
+    public static final Long INNER_PROFIT = 5L;
+    
+    
+    /**
+     * 用户默认头像
+     */
+    public static final String ICON_USER = "/public/user.png";
+    
+    /**
+     * 支付回调锁
+     */
+    public static final String TF_PAY_NOTIFY_LOCK = "TF_PAY_NOTIFY_LOCK:%s";
+    
+    /**
+     * 提现提交锁
+     */
+    public static final String TF_PAY_WITHDRAW_SUBMIT_LOCK = "TF_PAY_WITHDRAW_SUBMIT_LOCK:%s";
+    
+    /**
+     * 提现回调锁
+     */
+    public static final String TF_PAY_WITHDRAW_NOTIFY_LOCK = "TF_PAY_WITHDRAW_NOTIFY_LOCK:%s";
 }

+ 5 - 0
cif-service/src/main/java/com/txz/cif/model/PaymentChannel.java

@@ -78,6 +78,11 @@ public class PaymentChannel {
      */
     private String notifyUrl;
     
+    /**
+     * 提现异步通知地址
+     */
+    private String withdrawNotifyUrl;
+    
     /**
      * 密钥
      */

+ 6 - 2
cif-service/src/main/java/com/txz/cif/model/WithdrawRecord.java

@@ -4,9 +4,9 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.*;
 
+import javax.persistence.*;
 import java.math.BigDecimal;
 import java.util.Date;
-import javax.persistence.*;
 @Getter
 @Setter
 @NoArgsConstructor
@@ -155,6 +155,10 @@ public class WithdrawRecord {
     @Column(name = "bank_account")
     @ApiModelProperty(value="银行")
     private String bankAccount;
-
+    
+    /**
+     * 三方订单号
+     */
+    private String thirdOrderNo;
 
 }

+ 1 - 0
cif-service/src/main/java/com/txz/cif/service/TFPayService.java

@@ -25,6 +25,7 @@ public interface TFPayService {
     /**
      * 银行列表
      */
+    @Deprecated
     String queryBankList(String payChannelcode);
     
     /**

+ 6 - 5
cif-service/src/main/java/com/txz/cif/service/WithdrawRecordService.java

@@ -1,4 +1,5 @@
 package com.txz.cif.service;
+
 import com.txz.cif.model.WithdrawRecord;
 import com.txz.cif.core.Service;
 
@@ -7,12 +8,12 @@ import com.txz.cif.core.Service;
  * Created by CodeGenerator on 2025/07/15.
  */
 public interface WithdrawRecordService extends Service<WithdrawRecord> {
-
+    
     void add(WithdrawRecord withdrawRecord);
-
-    void review(WithdrawRecord build);
-
+    
+    void review(WithdrawRecord build, Integer status, String review, Long channelId);
+    
     void success(WithdrawRecord record);
-
+    
     void fail(WithdrawRecord record);
 }

+ 5 - 4
cif-service/src/main/java/com/txz/cif/service/impl/TFPayServiceImpl.java

@@ -77,6 +77,7 @@ public class TFPayServiceImpl implements TFPayService {
         return result;
     }
     
+    @Deprecated
     @Override
     public String queryBankList(String payChannelcode) {
         TFPayOrderDTO tfPay = TFPayOrderDTO.builder()
@@ -108,13 +109,13 @@ public class TFPayServiceImpl implements TFPayService {
                 .build();
         
         // 生成签名
-        withdrawDTO.setPayMd5sign(TFPayUtil.generateSignature(dto, key));
-        withdrawDTO.setPayBankcode(dto.getPayBankcode());
+        withdrawDTO.setPayMd5sign(TFPayUtil.generateSignature(withdrawDTO, key));
+        withdrawDTO.setPayBankcode(dto.getPayBankcode().toLowerCase());
         withdrawDTO.setCurrencyType("BDT");
         withdrawDTO.setType("json");
         
-        Map<String, Object> reqMap = TFPayUtil.convertObjectToMap(dto);
-        String result = HttpRequest.post(tfPayConfig.getWithdrawurl())
+        Map<String, Object> reqMap = TFPayUtil.convertObjectToMap(withdrawDTO);
+        String result = HttpRequest.post(tfPayConfig.getWithdrawurl() + "/pay")
                 .form(reqMap)
                 .contentType("application/x-www-form-urlencoded")
                 .execute()

+ 129 - 40
cif-service/src/main/java/com/txz/cif/service/impl/WithdrawRecordServiceImpl.java

@@ -1,50 +1,70 @@
 package com.txz.cif.service.impl;
 
+import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
-import com.txz.cif.dao.WithdrawRecordMapper;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockTemplate;
+import com.baomidou.lock.executor.RedisTemplateLockExecutor;
+import com.txz.cif.constants.MyConstants;
+import com.txz.cif.core.AbstractService;
+import com.txz.cif.core.ServiceException;
+import com.txz.cif.dto.BizLogDTO;
 import com.txz.cif.dto.Result;
+import com.txz.cif.dto.tfpay.TFWithdrawDTO;
 import com.txz.cif.enums.BizTypeEnum;
-import com.txz.cif.model.Account;
-import com.txz.cif.model.AccountFreezd;
-import com.txz.cif.model.WithdrawRecord;
+import com.txz.cif.model.*;
 import com.txz.cif.param.FreezdParam;
 import com.txz.cif.param.WithdrawParam;
-import com.txz.cif.service.AccountFreezdService;
-import com.txz.cif.service.AccountService;
-import com.txz.cif.service.FlowService;
-import com.txz.cif.service.WithdrawRecordService;
-import com.txz.cif.core.AbstractService;
+import com.txz.cif.service.*;
+import com.txz.cif.util.TFPayUtil;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.math.BigDecimal;
-import java.util.Date;
+import java.util.Map;
 
 
 /**
  * Created by CodeGenerator on 2025/07/15.
  */
 @Service
+@Slf4j
 @Transactional
 public class WithdrawRecordServiceImpl extends AbstractService<WithdrawRecord> implements WithdrawRecordService {
-    @Resource
-    private WithdrawRecordMapper cWithdrawRecordMapper;
-
-    @Resource
-    private AccountFreezdService accountFreezdService;
-
+    
     @Resource
     private AccountService accountService;
-
+    
     @Resource
     private FlowService flowService;
-
+    
+    @Resource
+    private PaymentChannelService paymentChannelService;
+    
+    @Resource
+    private SequenceService sequenceService;
+    
+    @Resource
+    private UserService userService;
+    
+    @Resource
+    private TFPayService tfPayService;
+    
+    @Resource
+    private BizLogService bizLogService;
+    
+    @Resource
+    private LockTemplate lockTemplate;
+    
     @Override
     public void add(WithdrawRecord withdrawRecord) {
         saveUseGeneratedKeys(withdrawRecord);
         Account account = accountService.getAccount(withdrawRecord.getUserId(), withdrawRecord.getAccountType());
-        //冻结金额+手续费
+        // 冻结金额+手续费
         Result freeze = flowService.freeze(FreezdParam.builder().accountType(withdrawRecord.getAccountType())
                 .amount(withdrawRecord.getAmount().add(withdrawRecord.getFee())).bizType(BizTypeEnum.WITHDRAW.getKey())
                 .bizId(withdrawRecord.getId() + "").bizNo(withdrawRecord.getOrderNo())
@@ -53,39 +73,108 @@ public class WithdrawRecordServiceImpl extends AbstractService<WithdrawRecord> i
                 .build());
         update(WithdrawRecord.builder().id(withdrawRecord.getId()).freezeId(Long.valueOf(freeze.getData().toString())).build());
     }
-
+    
     @Override
-    public void review(WithdrawRecord record) {
-        if (record.getStatus() == 2){
-            //TODO 审核通过 发起第三方提现
-
-        } else {
-            //解冻
-            if (record.getFreezeId() != null){
-                flowService.unFreeze(record.getFreezeId());
+    public void review(WithdrawRecord w, Integer status, String review, Long channelId) {
+        // 同意提现
+        if (status == 2) {
+            LockInfo lockInfo = lockTemplate.lock(String.format(MyConstants.TF_PAY_WITHDRAW_SUBMIT_LOCK, w.getOrderNo()), Long.valueOf(1000 * 60 * 2), Long.valueOf(1000 * 1), RedisTemplateLockExecutor.class);
+            if (null == lockInfo) {
+                throw new ServiceException("提现审核失败");
+            }
+            try {
+                User user = userService.findById(w.getUserId());
+                DateTime now = DateUtil.date();
+                String thirdOrderNo = sequenceService.genSerialNumber("withdraw_rule", null);
+                
+                PaymentChannel paymentChannel = paymentChannelService.findById(channelId);
+                if (ObjectUtil.isEmpty(paymentChannel)) {
+                    throw new ServiceException("提现渠道不存在");
+                }
+                switch (paymentChannel.getChannelName()) {
+                    case "TFPAY":
+                        Map<String, Object> payMentChannelMap = TFPayUtil.convertObjectToMap(paymentChannel);
+                        BizLogDTO bizLog = tfPayService.withdraw(
+                                TFWithdrawDTO.builder()
+                                        .payMemberid(paymentChannel.getMerchantNum())
+                                        .payOrderid(thirdOrderNo)
+                                        .payApplydate(now.toString())
+                                        // 如需测试改成502
+                                        // .payChannelcode("502")
+                                        .payChannelcode(payMentChannelMap.get(w.getChannel().toLowerCase() + "WithdrawCode").toString())
+                                        .payNotifyurl(paymentChannel.getWithdrawNotifyUrl())
+                                        .payAmount(w.getAmount().toString())
+                                        .payProductname("提现")
+                                        .payUsername(w.getUserName())
+                                        .payBanknumber(user.getBankAccount())
+                                        .payBankname(user.getBank())
+                                        .payBankcode(w.getChannel().toLowerCase())
+                                        .build()
+                                , paymentChannel.getSecretKey());
+                        try {
+                            // 新增日志
+                            bizLogService.save(BizLog.builder()
+                                    .bizType(2)
+                                    .bizNo(thirdOrderNo)
+                                    .type(4)
+                                    .receiveMessage(bizLog.getReceiveMessage())
+                                    .sendMessage(bizLog.getSendMessage())
+                                    .createTime(now)
+                                    .createUser(user.getName())
+                                    .memo("提交提现申请").build());
+                        } catch (Exception e) {
+                            log.error("新增提现提交订单日志失败", e);
+                        }
+                        JSONObject resultJson;
+                        try {
+                            resultJson = JSONUtil.parseObj(bizLog.getReceiveMessage());
+                        } catch (Exception e) {
+                            log.error("提现失败三方返回-->" + bizLog);
+                            throw new ServiceException("提现失败");
+                        }
+                        if (resultJson.getStr("status").equals("200")) {
+                            this.update(WithdrawRecord.builder().id(w.getId()).review(review).status(status).thirdOrderNo(thirdOrderNo).build());
+                        } else {
+                            log.error("提现失败--->" + resultJson.getStr("msg"));
+                            throw new RuntimeException("提现失败--->" + resultJson.getStr("msg"));
+                        }
+                        break;
+                    default:
+                        throw new ServiceException("暂不支持此渠道");
+                }
+                
+            } finally {
+                lockTemplate.releaseLock(lockInfo);
             }
+            return;
+        }
+        // 解冻
+        if (w.getFreezeId() != null) {
+            flowService.unFreeze(w.getFreezeId());
         }
-        update(record);
+        this.update(WithdrawRecord.builder().id(w.getId())
+                .review(review).status(status).build());
     }
-
+    
     @Override
     public void success(WithdrawRecord record) {
-        if (record.getFreezeId() != null){
+        if (record.getFreezeId() != null) {
             flowService.unFreeze(record.getFreezeId());
         }
         flowService.withdraw(WithdrawParam.builder()
-                        .accountType(record.getAccountType()).bizType(BizTypeEnum.WITHDRAW.getKey())
-                        .userId(record.getUserId())
-                        .amount(record.getAmount())
-                        .bizId(record.getId()+"")
-                        .bizNo(record.getOrderNo())
-                        .transTime(record.getTransTime())
+                .accountType(record.getAccountType()).bizType(BizTypeEnum.WITHDRAW.getKey())
+                .userId(record.getUserId())
+                .amount(record.getAmount())
+                .bizId(record.getId() + "")
+                .bizNo(record.getOrderNo())
+                .transTime(record.getTransTime())
                 .build());
+        update(WithdrawRecord.builder().id(record.getId()).status(4).build());
     }
-
+    
     @Override
     public void fail(WithdrawRecord record) {
-        if (record.getFreezeId() != null){
+        if (record.getFreezeId() != null) {
             flowService.unFreeze(record.getFreezeId());
         }
         update(WithdrawRecord.builder().id(record.getId()).status(5).build());

+ 3 - 3
cif-service/src/main/java/com/txz/cif/web/RechargeRecordApiController.java

@@ -222,12 +222,12 @@ public class RechargeRecordApiController {
             criteria.andEqualTo("isValid", 1);
             List<PaymentChannel> paymentChannels = paymentChannelService.findByCondition(condition);
             if (CollectionUtil.isEmpty(paymentChannels)) {
-                log.info("支付渠道不存在");
+                log.error("支付渠道不存在");
                 return "FAIL";
             }
             boolean checkSignResult = TFPayUtil.verifySignature(BeanUtil.copyProperties(dto, CheckSignDTO.class), paymentChannels.get(0).getSecretKey(), dto.getSign());
             if (!checkSignResult) {
-                log.info("签名校验失败");
+                log.error("签名校验失败");
                 return "FAIL";
             }
             RechargeRecord record = rechargeRecordService.findBy("orderNo", dto.getOrderid());
@@ -265,7 +265,7 @@ public class RechargeRecordApiController {
             }
             return "OK";
         } finally {
-            this.lockTemplate.releaseLock(lockInfo);
+            lockTemplate.releaseLock(lockInfo);
         }
     }
     

+ 262 - 192
cif-service/src/main/java/com/txz/cif/web/WithdrawRecordApiController.java

@@ -1,31 +1,37 @@
 package com.txz.cif.web;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockTemplate;
+import com.baomidou.lock.executor.RedisTemplateLockExecutor;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.txz.cif.constants.MyConstants;
+import com.txz.cif.core.AuthService;
 import com.txz.cif.core.Result;
+import com.txz.cif.core.ResultCode;
 import com.txz.cif.core.ResultGenerator;
+import com.txz.cif.dto.tfpay.CheckWithdrawSignDTO;
 import com.txz.cif.dubbo.client.OperatingConfigDubboServiceClient;
 import com.txz.cif.model.*;
 import com.txz.cif.service.*;
-
-import com.txz.cif.core.ResultCode;
-
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
+import com.txz.cif.util.TFPayUtil;
 import com.txz.cif.web.para.RecordParam;
 import com.txz.cif.web.para.WithdrawParam;
-import com.txz.cif.core.AuthService;
+import com.txz.cif.web.ro.TFWithdrawCallbackDTO;
 import com.txz.operating.dto.ConfigDTO;
 import io.jsonwebtoken.Claims;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang3.tuple.Triple;
-import org.springframework.web.bind.annotation.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-
+import org.springframework.web.bind.annotation.*;
 import tk.mybatis.mapper.entity.Condition;
+import tk.mybatis.mapper.entity.Example;
 import tk.mybatis.mapper.entity.Example.Criteria;
 
 import javax.annotation.Resource;
@@ -37,193 +43,257 @@ import java.util.List;
 import static com.txz.cif.core.AbstractApiController.X_CLIENT_TOKEN;
 
 /**
-* Created by CodeGenerator on 2025/07/15.
-*/
+ * Created by CodeGenerator on 2025/07/15.
+ */
 @Api(tags = "[api]提现管理")
 @RestController
 @RequestMapping("/api/withdraw/record")
 public class WithdrawRecordApiController {
-
-	private static Logger log = LoggerFactory.getLogger(WithdrawRecordApiController.class);
-
+    
+    private static Logger log = LoggerFactory.getLogger(WithdrawRecordApiController.class);
+    
     @Resource
     private WithdrawRecordService withdrawRecordService;
-
-	@Resource
-	private SequenceService sequenceService;
-
-	@Resource
-	private AuthService authService;
-
-	@Resource
-	private AccountService accountService;
-
-	@Resource
-	private BizLogService bizLogService;
-
-	@Resource
-	private UserService userService;
-
-
-	@Resource
-	private OperatingConfigDubboServiceClient operatingConfigDubboServiceClient;
-
+    
+    @Resource
+    private SequenceService sequenceService;
+    
+    @Resource
+    private AuthService authService;
+    
+    @Resource
+    private AccountService accountService;
+    
+    @Resource
+    private BizLogService bizLogService;
+    
+    @Resource
+    private PaymentChannelService paymentChannelService;
+    
+    @Resource
+    private UserService userService;
+    
+    @Resource
+    private LockTemplate lockTemplate;
+    
+    @Resource
+    private OperatingConfigDubboServiceClient operatingConfigDubboServiceClient;
+    
     @PostMapping("/add")
-	@ApiOperation(value = "新增提现订单",httpMethod = "POST")
-    public Result add(@RequestBody WithdrawParam param,@RequestHeader(value = X_CLIENT_TOKEN,required = false) String token) {
-    	if(param == null){
-    		return ResultGenerator.genFailResult(ResultCode.OBJECT_IS_NULL);
-    	}
-    	try {
-
-
-			Triple<Boolean, String, Claims> result = authService.verifyToken(token);
-
-			if (!result.getLeft()) {
-				// token无效
-				String errorMsg = result.getMiddle();
-				// 处理错误情况...
-				return ResultGenerator.genFailResult(ResultCode.OAUTH_INVALID_ACCESS_TOKEN);
-			}
-			Claims claims = result.getRight();
-			Long userId = Long.valueOf(claims.get("userId").toString());
-			User user = userService.findById(userId);
-			if (user == null){
-				return ResultGenerator.genFailResult(ResultCode.USER_IS_NULL);
-			}
-			if(StrUtil.isBlank(user.getBank()) ){
-				if (StrUtil.isBlank(param.getBank()) || StrUtil.isBlank(param.getBankAccount())  ||StrUtil.isBlank(param.getBankAccountName()) ) {
-					return ResultGenerator.genFailResult(ResultCode.BANK_IS_NULL);
-				}
-				userService.update(User.builder().
-						id(user.getId()).bank(param.getBank())
-						.bankAccountName(param.getBankAccountName())
-						.bankAccount(param.getBankAccount()).build());
-				user.setBank(param.getBank());
-				user.setBankAccount(param.getBankAccount());
-				user.setBankAccountName(param.getBankAccountName());
-			}
-
-
-			com.txz.operating.result.Result<ConfigDTO> openRedEnvelopeRate = operatingConfigDubboServiceClient.getConfigByCode("join_red_envelope_rate");
-			String rate = openRedEnvelopeRate.getData().getValueInfo();
-			BigDecimal fee = param.getAmount().multiply(new BigDecimal(rate)).divide(BigDecimal.valueOf(100),2, RoundingMode.DOWN);
-
-			Account account = accountService.getAccount(userId, param.getAccountType());
-			if (account == null){
-				return ResultGenerator.genFailResult(ResultCode.ACCOUNT_IS_NULL);
-			}
-			if (account.getEffectiveBalance().compareTo(param.getAmount().add(fee)) <0) {
-				return ResultGenerator.genFailResult(ResultCode.EFFECTIVE_BALANCE_IS_INSUFFICIENT);
-			}
-
-			String orderNo = sequenceService.genSerialNumber("withdraw_rule",null);
-
-			WithdrawRecord withdrawRecord = new WithdrawRecord();
-			withdrawRecord.setOrderNo(orderNo);
-			withdrawRecord.setStatus(1);
-			withdrawRecord.setUserName(user.getName());
-			withdrawRecord.setUserPhone(user.getPhoneNo());
-			withdrawRecord.setTransTime(DateUtil.date());
-			withdrawRecord.setUserId(userId);
-			withdrawRecord.setAccountType(param.getAccountType());
-			withdrawRecord.setFee(fee);
-			withdrawRecord.setBank(user.getBank());
-			withdrawRecord.setBankAccount(user.getBankAccount());
-			withdrawRecord.setBankAccountName(user.getBankAccountName());
-			withdrawRecord.setChannel(param.getChannel());
-			withdrawRecord.setCurrency(param.getCurrency());
-			withdrawRecord.setAmount(param.getAmount());
-    		withdrawRecord.setCreateTime(DateUtil.date());
-    		withdrawRecordService.add(withdrawRecord);
-			return ResultGenerator.genSuccessResult(withdrawRecord);
-		} catch (Exception e) {
-			log.error("新增对象操作异常e:{}",e);
-			return ResultGenerator.genFailResult(ResultCode.INTERNAL_SERVER_ERROR);
-		}
-
+    @ApiOperation(value = "新增提现订单", httpMethod = "POST")
+    public Result add(@RequestBody WithdrawParam param, @RequestHeader(value = X_CLIENT_TOKEN, required = false) String token) {
+        if (param == null) {
+            return ResultGenerator.genFailResult(ResultCode.OBJECT_IS_NULL);
+        }
+        try {
+            Triple<Boolean, String, Claims> result = authService.verifyToken(token);
+            
+            if (!result.getLeft()) {
+                // token无效
+                String errorMsg = result.getMiddle();
+                // 处理错误情况...
+                return ResultGenerator.genFailResult(ResultCode.OAUTH_INVALID_ACCESS_TOKEN);
+            }
+            Claims claims = result.getRight();
+            Long userId = Long.valueOf(claims.get("userId").toString());
+            User user = userService.findById(userId);
+            if (user == null) {
+                return ResultGenerator.genFailResult(ResultCode.USER_IS_NULL);
+            }
+            if (StrUtil.isBlank(user.getBank())) {
+                if (StrUtil.isBlank(param.getBank()) || StrUtil.isBlank(param.getBankAccount()) || StrUtil.isBlank(param.getBankAccountName())) {
+                    return ResultGenerator.genFailResult(ResultCode.BANK_IS_NULL);
+                }
+                userService.update(User.builder().
+                        id(user.getId()).bank(param.getBank())
+                        .bankAccountName(param.getBankAccountName())
+                        .bankAccount(param.getBankAccount()).build());
+                user.setBank(param.getBank());
+                user.setBankAccount(param.getBankAccount());
+                user.setBankAccountName(param.getBankAccountName());
+            }
+            
+            
+            com.txz.operating.result.Result<ConfigDTO> openRedEnvelopeRate = operatingConfigDubboServiceClient.getConfigByCode("join_red_envelope_rate");
+            String rate = openRedEnvelopeRate.getData().getValueInfo();
+            BigDecimal fee = param.getAmount().multiply(new BigDecimal(rate)).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
+            
+            Account account = accountService.getAccount(userId, param.getAccountType());
+            if (account == null) {
+                return ResultGenerator.genFailResult(ResultCode.ACCOUNT_IS_NULL);
+            }
+            if (account.getEffectiveBalance().compareTo(param.getAmount().add(fee)) < 0) {
+                return ResultGenerator.genFailResult(ResultCode.EFFECTIVE_BALANCE_IS_INSUFFICIENT);
+            }
+            
+            String orderNo = sequenceService.genSerialNumber("withdraw_rule", null);
+            
+            WithdrawRecord withdrawRecord = new WithdrawRecord();
+            withdrawRecord.setOrderNo(orderNo);
+            withdrawRecord.setStatus(1);
+            withdrawRecord.setUserName(user.getName());
+            withdrawRecord.setUserPhone(user.getPhoneNo());
+            withdrawRecord.setTransTime(DateUtil.date());
+            withdrawRecord.setUserId(userId);
+            withdrawRecord.setAccountType(param.getAccountType());
+            withdrawRecord.setFee(fee);
+            withdrawRecord.setBank(user.getBank());
+            withdrawRecord.setBankAccount(user.getBankAccount());
+            withdrawRecord.setBankAccountName(user.getBankAccountName());
+            withdrawRecord.setChannel(param.getChannel());
+            withdrawRecord.setCurrency(param.getCurrency());
+            withdrawRecord.setAmount(param.getAmount());
+            withdrawRecord.setCreateTime(DateUtil.date());
+            withdrawRecordService.add(withdrawRecord);
+            return ResultGenerator.genSuccessResult(withdrawRecord);
+        } catch (Exception e) {
+            log.error("新增对象操作异常e:{}", e);
+            return ResultGenerator.genFailResult(ResultCode.INTERNAL_SERVER_ERROR);
+        }
+        
     }
-
-
+    
+    
     @PostMapping("/list")
-	@ApiOperation(value = "withdrawRecord获取列表",httpMethod = "POST")
-	public Result<List<WithdrawRecord>> list(@RequestBody RecordParam param, HttpServletRequest request) {
-		Long userId = authService.getTokenUserId(request);
-		if (userId == null ){
-			ResultGenerator.genFailResult(ResultCode.OAUTH_INVALID_ACCESS_TOKEN);
-		}
-		PageHelper.startPage(param.getPage(), param.getSize());
-
-		Condition condition = new Condition(WithdrawRecord.class);
-		Criteria criteria = condition.createCriteria();
-		criteria.andEqualTo("userId", userId);
-		if (StrUtil.isNotBlank(param.getChannel())){
-			criteria.andEqualTo("channel", param.getChannel());
-		}
-		if (StrUtil.isNotBlank(param.getUserName())){
-			criteria.andEqualTo("userName", param.getUserName());
-		}
-		if (StrUtil.isNotBlank(param.getUserPhone())){
-			criteria.andEqualTo("userPhone", param.getUserPhone());
-		}
-		if (StrUtil.isNotBlank(param.getOrderNo())){
-			criteria.andEqualTo("orderNo", param.getOrderNo());
-		}
-		if (param.getStatus() != null){
-			criteria.andEqualTo("status", param.getStatus());
-		}
-		if (param.getAccountType() != null){
-			criteria.andEqualTo("accountType", param.getAccountType());
-		}
-		if (param.getTimeType() != null){
-			if (param.getTimeType() ==1 ){
-				if (StrUtil.isNotBlank(param.getStartTime())){
-					criteria.andBetween("createTime", param.getStartTime(),param.getEndTime());
-				}
-			} else  if (param.getTimeType() ==2 ){
-				if (StrUtil.isNotBlank(param.getStartTime())){
-					criteria.andBetween("successTime", param.getStartTime(),param.getEndTime());
-				}
-			}
-		}
-		PageInfo pageInfo = null;
-		try {
-			condition.setOrderByClause("create_time desc");
-			List<WithdrawRecord> list = withdrawRecordService.findByCondition(condition);
-			pageInfo = new PageInfo(list);
-		} catch (Exception e) {
-			log.error("查询对象操作异常e:{}",e);
-			return ResultGenerator.genFailResult(ResultCode.INTERNAL_SERVER_ERROR);
-		}
-		return ResultGenerator.genSuccessResult(pageInfo);
-	}
-
-	@GetMapping("/callback")
-	@ApiOperation(value = "三方回调",httpMethod = "GET")
-	public Result<RechargeRecord> callback(@RequestParam String data) {
-		//TODO 回调成功
-		JSONObject json = JSONUtil.parseObj(data);
-		String orderNo = json.getStr("orderNo");
-		WithdrawRecord record = withdrawRecordService.findBy("orderNo", orderNo);
-		if (record == null){
-			return ResultGenerator.genFailResult("订单未找到");
-		}
-		Integer type= 8;
-		if (StrUtil.equals("1",json.getStr("status"))){
-			withdrawRecordService.success(record);
-		} else {
-			withdrawRecordService.fail(record);
-			type= 9;
-		}
-		try{
-			//新增充值回调日志
-			bizLogService.save(BizLog.builder().bizType(2).bizNo(orderNo)
-					.type(type).createTime(DateUtil.date()).createUser("第三方")
-					.memo("提现回调").build());
-		}catch (Exception e){
-			log.error("新增充值回调日志失败",e);
-		}
-		return ResultGenerator.genSuccessResult();
-	}
-
+    @ApiOperation(value = "withdrawRecord获取列表", httpMethod = "POST")
+    public Result<List<WithdrawRecord>> list(@RequestBody RecordParam param, HttpServletRequest request) {
+        Long userId = authService.getTokenUserId(request);
+        if (userId == null) {
+            ResultGenerator.genFailResult(ResultCode.OAUTH_INVALID_ACCESS_TOKEN);
+        }
+        PageHelper.startPage(param.getPage(), param.getSize());
+        
+        Condition condition = new Condition(WithdrawRecord.class);
+        Criteria criteria = condition.createCriteria();
+        criteria.andEqualTo("userId", userId);
+        if (StrUtil.isNotBlank(param.getChannel())) {
+            criteria.andEqualTo("channel", param.getChannel());
+        }
+        if (StrUtil.isNotBlank(param.getUserName())) {
+            criteria.andEqualTo("userName", param.getUserName());
+        }
+        if (StrUtil.isNotBlank(param.getUserPhone())) {
+            criteria.andEqualTo("userPhone", param.getUserPhone());
+        }
+        if (StrUtil.isNotBlank(param.getOrderNo())) {
+            criteria.andEqualTo("orderNo", param.getOrderNo());
+        }
+        if (param.getStatus() != null) {
+            criteria.andEqualTo("status", param.getStatus());
+        }
+        if (param.getAccountType() != null) {
+            criteria.andEqualTo("accountType", param.getAccountType());
+        }
+        if (param.getTimeType() != null) {
+            if (param.getTimeType() == 1) {
+                if (StrUtil.isNotBlank(param.getStartTime())) {
+                    criteria.andBetween("createTime", param.getStartTime(), param.getEndTime());
+                }
+            } else if (param.getTimeType() == 2) {
+                if (StrUtil.isNotBlank(param.getStartTime())) {
+                    criteria.andBetween("successTime", param.getStartTime(), param.getEndTime());
+                }
+            }
+        }
+        PageInfo pageInfo = null;
+        try {
+            condition.setOrderByClause("create_time desc");
+            List<WithdrawRecord> list = withdrawRecordService.findByCondition(condition);
+            pageInfo = new PageInfo(list);
+        } catch (Exception e) {
+            log.error("查询对象操作异常e:{}", e);
+            return ResultGenerator.genFailResult(ResultCode.INTERNAL_SERVER_ERROR);
+        }
+        return ResultGenerator.genSuccessResult(pageInfo);
+    }
+    
+    /**
+     * tfpay提现通知
+     */
+    @PostMapping("/callback/tfpay")
+    public String callback(@ModelAttribute TFWithdrawCallbackDTO dto) {
+        LockInfo lockInfo = lockTemplate.lock(String.format(MyConstants.TF_PAY_WITHDRAW_NOTIFY_LOCK, dto.getOrderid()), Long.valueOf(1000 * 60 * 2), Long.valueOf(1000 * 1), RedisTemplateLockExecutor.class);
+        if (null == lockInfo) {
+            return "WAIT";
+        }
+        try {
+            log.info("提现渠道--->tfpay,提现回调通知--->" + dto);
+            Condition channelCondition = new Condition(PaymentChannel.class);
+            Example.Criteria channelCriteria = channelCondition.createCriteria();
+            channelCriteria.andEqualTo("channelName", "TFPAY");
+            channelCriteria.andEqualTo("isValid", 1);
+            List<PaymentChannel> paymentChannels = paymentChannelService.findByCondition(channelCondition);
+            if (CollectionUtil.isEmpty(paymentChannels)) {
+                log.error("支付渠道不存在");
+                return "FAIL";
+            }
+            boolean checkSignResult = TFPayUtil.verifySignature(BeanUtil.copyProperties(dto, CheckWithdrawSignDTO.class), paymentChannels.get(0).getSecretKey(), dto.getSign());
+            
+            if (!checkSignResult) {
+                log.error("签名校验失败");
+                return "FAIL";
+            }
+            
+            Condition condition = new Condition(WithdrawRecord.class);
+            Example.Criteria criteria = condition.createCriteria();
+            criteria.andEqualTo("thirdOrderNo", dto.getOrderid());
+            criteria.andEqualTo("status", 2);
+            List<WithdrawRecord> records = withdrawRecordService.findByCondition(condition);
+            if (CollectionUtil.isEmpty(records)) {
+                log.error("提现订单不存在");
+                return "FAIL";
+            }
+            
+            // //TODO 回调成功
+            // JSONObject json = JSONUtil.parseObj(data);
+            // String orderNo = json.getStr("orderNo");
+            // WithdrawRecord record = withdrawRecordService.findBy("orderNo", orderNo);
+            // if (record == null) {
+            //     return ResultGenerator.genFailResult("订单未找到");
+            // }
+            // Integer type = 8;
+            // if (StrUtil.equals("1", json.getStr("status"))) {
+            //     withdrawRecordService.success(record);
+            // } else {
+            //     withdrawRecordService.fail(record);
+            //     type = 9;
+            // }
+            // try {
+            //     // 新增充值回调日志
+            //     bizLogService.save(BizLog.builder().bizType(2).bizNo(orderNo)
+            //             .type(type).createTime(DateUtil.date()).createUser("第三方")
+            //             .memo("提现回调").build());
+            // } catch (Exception e) {
+            //     log.error("新增充值回调日志失败", e);
+            // }
+            // return ResultGenerator.genSuccessResult();
+            
+            Integer type = 8;
+            if (StrUtil.equals("1", dto.getReturncode())) {
+                withdrawRecordService.success(records.get(0));
+            } else {
+                withdrawRecordService.fail(records.get(0));
+                type = 9;
+            }
+            try {
+                // 新增充值回调日志
+                bizLogService.save(BizLog.builder()
+                        .bizType(2)
+                        .bizNo(dto.getOrderid())
+                        .type(type)
+                        .receiveMessage(dto.toString())
+                        .createTime(DateUtil.date())
+                        .createUser("TFPAY")
+                        .memo("提现回调").build());
+            } catch (Exception e) {
+                log.error("新增充值回调日志失败", e);
+                // }
+                
+            }
+            return "OK";
+        } finally {
+            lockTemplate.releaseLock(lockInfo);
+        }
+    }
+    
 }

+ 3 - 12
cif-service/src/main/java/com/txz/cif/web/mng/WithdrawRecordController.java

@@ -17,7 +17,6 @@ import com.txz.cif.model.RechargeRecord;
 import com.txz.cif.model.WithdrawRecord;
 import com.txz.cif.service.AccountService;
 import com.txz.cif.service.BizLogService;
-import com.txz.cif.service.TFPayService;
 import com.txz.cif.service.WithdrawRecordService;
 import com.txz.cif.web.bo.WithdrawRecordBO;
 import com.txz.cif.web.para.RecordParam;
@@ -55,9 +54,6 @@ public class WithdrawRecordController {
     @Resource
     private AccountService accountService;
     
-    @Resource
-    private TFPayService tfPayService;
-    
     @GetMapping("/detail")
     @ApiOperation(value = "withdrawRecord获取详情", httpMethod = "GET")
     public Result<WithdrawRecordBO> detail(@RequestParam Long id) {
@@ -118,14 +114,9 @@ public class WithdrawRecordController {
             return ResultGenerator.genFailResult(ResultCode.STATUS_IS_NULL);
         }
         
-        if (param.getType() == 1) {
-            // 同意提现
-            
-            // tfPayService.withdraw();
-        }
-        
-        withdrawRecordService.review(WithdrawRecord.builder().id(param.getId())
-                .review(param.getReview()).status(param.getType() == 1 ? 2 : 3).build());
+        withdrawRecordService.review(w, param.getType() == 1 ? 2 : 3, param.getReview(), param.getChannelId());
+        // WithdrawRecord.builder().id(param.getId())
+        // .review(param.getReview()).status(param.getType() == 1 ? 2 : 3).build());
         return ResultGenerator.genSuccessResult();
     }
     

+ 0 - 5
cif-service/src/main/java/com/txz/cif/web/para/ReviewParam.java

@@ -30,9 +30,4 @@ public class ReviewParam {
      */
     private Long channelId;
     
-    /**
-     * 提现code
-     */
-    private String withdrawCode;
-    
 }

+ 71 - 0
cif-service/src/main/java/com/txz/cif/web/ro/TFWithdrawCallbackDTO.java

@@ -0,0 +1,71 @@
+package com.txz.cif.web.ro;
+
+import lombok.Data;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/3
+ */
+@Data
+public class TFWithdrawCallbackDTO {
+    
+    /**
+     * 商户编号 - 平台分配商户号
+     */
+    private String memberid;
+    
+    /**
+     * 商户订单号 - 订单号唯一, 字符长度20
+     */
+    private String orderid;
+    
+    /**
+     * 订单金额 - 订单金额 单位:元
+     */
+    private String amount;
+    
+    /**
+     * 平台订单号
+     */
+    private String transaction_id;
+    
+    /**
+     * 用户姓名 - 如:张三
+     */
+    private String pay_username;
+    
+    /**
+     * 银行账号 - 如:银行卡号码
+     */
+    private String pay_banknumber;
+    
+    /**
+     * 银行名称 - 如:工商银行
+     */
+    private String pay_bankname;
+    
+    /**
+     * 交易成功时间 - 时间格式:Y-m-d H:i:s
+     */
+    private String datetime;
+    
+    /**
+     * 交易状态 - 1为成功,-1失败,0处理中
+     */
+    private String returncode;
+    
+    /**
+     * 订单描述 - 如:失败原因
+     */
+    private String remark;
+    
+    /**
+     * 扩展返回 - 商户附加数据返回
+     */
+    private String attach;
+    
+    /**
+     * 签名
+     */
+    private String sign;
+}