ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/WechatConstants.java
@@ -64,11 +64,13 @@ * 微信发起转账(前缀) */ public static final String WE_CHAT_PAY_URL_PRE = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills"; public static final String WE_CHAT_PAY_QUERY_URL_PRE = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/"; /** * 微信商家转账到用户零钱接口地址(后缀) */ public static final String WE_CHAT_URL_SUF = "/v3/transfer/batches"; public static final String WE_CHAT_QUERY_URL_SUF = "/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/"; /** * 微信转账接口调用所返回的成功参数之一 ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/controller/FranchiseeController.java
@@ -29,6 +29,7 @@ import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.security.annotation.RequiresPermissions; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.common.security.utils.SecurityUtils; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import com.wechat.pay.java.core.exception.MalformedMessageException; @@ -1060,4 +1061,47 @@ return franchiseeService.updateBatchById(list) ? R.ok() : R.fail(); } /** * 设置加盟商操作密码 * */ @ApiOperation(value = "设置加盟商操作密码【202506】", tags = {"后台-加盟商管理"}) @PutMapping(value = "/setPayPassword") public R<String> setPayPassword(@RequestParam(value = "payPassword") String payPassword) { Long userid = tokenService.getLoginUser().getUserid(); SysUser sysUser = sysUserService.getById(userid); if(Objects.isNull(sysUser.getFranchiseeId())){ return R.fail("加盟商信息不存在!"); } Franchisee franchisee = franchiseeService.getById(sysUser.getFranchiseeId()); franchisee.setPayPassword(SecurityUtils.encryptPassword(payPassword)); return franchiseeService.updateById(franchisee) ? R.ok() : R.fail(); } /** * 设置加盟商操作密码 * */ @ApiOperation(value = "加盟商余额扣除撤回【202506】", tags = {"后台-加盟商管理"}) @PutMapping(value = "/balanceWithdraw") public R<String> balanceWithdraw(@RequestParam(value = "id") Integer id, @RequestParam(value = "payPassword") String payPassword) { TFranchiseeBalanceChange balanceChange = balanceChangeService.getById(id); Franchisee franchisee = franchiseeService.getById(balanceChange.getFranchiseeId()); if(!SecurityUtils.matchesPassword(payPassword, franchisee.getPayPassword())){ return R.fail("密码错误!"); } franchisee.setBalance(franchisee.getBalance().add(balanceChange.getAmount())); franchiseeService.updateById(franchisee); // 删除操作记录 balanceChangeService.removeById(id); return R.ok(); } } ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/entity/Franchisee.java
@@ -94,6 +94,9 @@ @ApiModelProperty("余额") @TableField("balance") private BigDecimal balance; @ApiModelProperty("操作密码") @TableField("payPassword") private String payPassword; @TableField(exist = false) private String siteStr; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/WxChatPayCallBack.java
@@ -113,14 +113,14 @@ orderService.updateById(order); }else if ("CANCELLED".equals(state)||"CANCELING".equals(state)||"FAIL".equals(state)){ // 校验提现 List<Withdraw> list = withdrawService.lambdaQuery().eq(Withdraw::getUserId, order.getUserId()) .eq(Withdraw::getOrderId, order.getId()).list(); List<Integer> stateList = list.stream().map(Withdraw::getState).collect(Collectors.toList()); if (stateList.contains(Constants.ONE)) { throw new GlobalException("当前订单提现申请已通过!"); } // List<Withdraw> list = withdrawService.lambdaQuery().eq(Withdraw::getUserId, order.getUserId()) // .eq(Withdraw::getOrderId, order.getId()).list(); // List<Integer> stateList = list.stream().map(Withdraw::getState).collect(Collectors.toList()); // if (stateList.contains(Constants.ONE)) { // throw new GlobalException("当前订单提现申请已通过!"); // } // 超时未收款 撤销 重新发起转账 更新packageInfo weChatPay(order.getOrderMoney(), data.getOpenId(),list.get(0).getId(),order.getServeName()); weChatPay(order.getOrderMoney(), data.getOpenId(),withdraw.getId(),order.getServeName()); } map.put("code", "SUCCESS"); map.put("message", "成功"); @@ -183,7 +183,7 @@ WithdrawDetail one = withdrawDetailService.lambdaQuery().eq(WithdrawDetail::getWithdrawId, withdrawId).last("limit 1").one(); if (one!=null){ one.setOutBatchNo(s); one.setStatus("FAIL"); one.setStatus("PENDING"); withdrawDetailService.updateById(one); Order order = orderService.getById(withdraw.getOrderId()); order.setPackageInfo(string); @@ -200,13 +200,16 @@ return allTransfersSuccessful; } // public static void main(String[] args) { // String s = "1827928ae317443a8ef788e9ed56e8dc"; // String s1 = HttpUtil.queryTransBatRequest(WechatConstants.WE_CHAT_PAY_QUERY_URL_PRE + s, // "7EEA04429B006E12AAA421C002EC48BBEED5BE94", // "1665330417", // "D:\\apiclient_key.pem", WechatConstants.WE_CHAT_QUERY_URL_SUF + s); // System.err.println(s1); // } public static void main(String[] args) { String s = "e965efcc225b4250864658d2fa969a18"; String s1 = HttpUtil.queryTransBatRequest(WechatConstants.WE_CHAT_PAY_QUERY_URL_PRE + s, "7EEA04429B006E12AAA421C002EC48BBEED5BE94", "1665330417", "D:\\apiclient_key.pem", WechatConstants.WE_CHAT_QUERY_URL_SUF + s); System.err.println(s1); JSONObject jsonObject = JSONObject.parseObject(s1); String string = jsonObject.getString("state"); System.err.println(string); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/entity/WithdrawRecord.java
New file @@ -0,0 +1,54 @@ package com.ruoyi.order.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.Getter; import lombok.Setter; import java.math.BigDecimal; import java.util.Date; /** * <p> * 用户提现流程 * </p> * * @author hjl * @since 2024-07-09 */ @Getter @Setter @TableName("t_withdraw_record") @ApiModel(value = "WithdrawRecord对象", description = "用户提现流程表") public class WithdrawRecord { @TableId(value = "id", type = IdType.AUTO) private Integer id; @ApiModelProperty("提现记录id") @TableField(value = "withdraw_id") private String withdrawId; @ApiModelProperty("订单id") @TableField("orderId") private String orderId; @ApiModelProperty("用户id") @TableField("userId") private Integer userId; @ApiModelProperty("类型 1=提现申请 2=平台审核 3=用户确认 4=提现成功") @TableField("withdrawType") private Integer withdrawType; @ApiModelProperty("创建时间") @TableField("createTime") private Date createTime; @ApiModelProperty("审核结果 1=通过 0=拒绝") @TableField("auditStatus") private Integer auditStatus; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/mapper/WithdrawRecordMapper.java
New file @@ -0,0 +1,20 @@ package com.ruoyi.order.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.order.entity.WithdrawDetail; import com.ruoyi.order.entity.WithdrawRecord; import org.apache.ibatis.annotations.Mapper; /** * <p> * 用户提现流程记录表 Mapper 接口 * </p> * * @author hjl * @since 2024-07-09 */ @Mapper public interface WithdrawRecordMapper extends BaseMapper<WithdrawRecord> { } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/WithdrawRecordService.java
New file @@ -0,0 +1,28 @@ package com.ruoyi.order.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.order.entity.WithdrawDetail; import com.ruoyi.order.entity.WithdrawRecord; /** * <p> * 用户提现申请记录表 服务类 * </p> * * @author hjl * @since 2024-07-09 */ public interface WithdrawRecordService extends IService<WithdrawRecord> { /** * 保存提现申请记录 * * @param withdrawId * @param orderId * @param userId * @param withdrawType * @param auditStatus */ void saveWithdrawRecord(String withdrawId, String orderId, Integer userId, Integer withdrawType, Integer auditStatus); } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WithdrawRecordServiceImpl.java
New file @@ -0,0 +1,37 @@ package com.ruoyi.order.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.order.entity.WithdrawDetail; import com.ruoyi.order.entity.WithdrawRecord; import com.ruoyi.order.mapper.WithdrawDetailMapper; import com.ruoyi.order.mapper.WithdrawRecordMapper; import com.ruoyi.order.service.WithdrawDetailService; import com.ruoyi.order.service.WithdrawRecordService; import org.springframework.stereotype.Service; import java.util.Date; /** * <p> * 用户提现申请记录表 服务实现类 * </p> * * @author hjl * @since 2024-07-09 */ @Service public class WithdrawRecordServiceImpl extends ServiceImpl<WithdrawRecordMapper, WithdrawRecord> implements WithdrawRecordService { @Override public void saveWithdrawRecord(String withdrawId, String orderId, Integer userId, Integer withdrawType, Integer auditStatus) { WithdrawRecord withdrawRecord = new WithdrawRecord(); withdrawRecord.setWithdrawId(withdrawId); withdrawRecord.setOrderId(orderId); withdrawRecord.setUserId(userId); withdrawRecord.setWithdrawType(withdrawType); withdrawRecord.setAuditStatus(auditStatus); withdrawRecord.setCreateTime(new Date()); this.save(withdrawRecord); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WithdrawServiceImpl.java
@@ -253,9 +253,9 @@ withdraw.setState(Constants.ONE); // 商家微信打款至微信零钱 boolean update = weChatPay(order.getOrderMoney(), openId,withdraw.getId(),order.getServerName()); // if (!update) { // throw new GlobalException("交易提现失败,请检查是否绑定微信!"); // } if (!update) { throw new GlobalException("交易提现失败,请检查是否绑定微信!"); } } else { // 待审核 withdraw.setState(Constants.ZERO); @@ -422,15 +422,12 @@ withdrawDetail.setMoney(transferAmount); withdrawDetail.setOutBatchNo(postMap.get("out_bill_no")+""); withdrawDetailService.save(withdrawDetail); } else { allTransfersSuccessful = false; break; throw new GlobalException("提现失败,失败原因:"+jsonObject.getString("message")); // allTransfersSuccessful = false; // break; } } return allTransfersSuccessful; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/task/TaskUtil.java
@@ -1,15 +1,30 @@ package com.ruoyi.order.task; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.constant.WechatConstants; import com.ruoyi.common.core.vo.UserDto; import com.ruoyi.order.entity.Order; import com.ruoyi.order.entity.Withdraw; import com.ruoyi.order.entity.WithdrawDetail; import com.ruoyi.order.service.OrderService; import com.ruoyi.order.service.WithdrawDetailService; import com.ruoyi.order.service.WithdrawService; import com.ruoyi.order.vx.GetTransferBatchByOutNo; import com.ruoyi.order.vx.HttpUtil; import com.ruoyi.user.api.feignClient.UserClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; /** @@ -20,42 +35,127 @@ @Resource private WithdrawDetailService withdrawDetailService; @Resource private WithdrawService withdrawService; @Resource private OrderService orderService; @Autowired private UserClient userClient; /** * 每隔一小时去处理的定时任务 */ @Scheduled(fixedRate = 1000 * 3600) public void taskMinute(){ try { List<Withdraw> list1 = withdrawService.list(); List<WithdrawDetail> list = withdrawDetailService.lambdaQuery() .isNotNull(WithdrawDetail::getOutBatchNo).ne(WithdrawDetail::getStatus, "SUCCESS").list(); for (WithdrawDetail withdrawDetail : list) { Withdraw withdraw = list1.stream().filter(e -> e.getId().equals(withdrawDetail.getWithdrawId())).findFirst().orElse(null); if (withdraw==null)continue; Order order = orderService.getById(withdraw.getOrderId()); UserDto data = userClient.getUser(order.getUserId()).getData(); String s = withdrawDetail.getOutBatchNo(); String s1 = HttpUtil.queryTransBatRequest(WechatConstants.WE_CHAT_PAY_QUERY_URL_PRE + s, "7EEA04429B006E12AAA421C002EC48BBEED5BE94", "1665330417", "D:\\apiclient_key.pem", WechatConstants.WE_CHAT_QUERY_URL_SUF + s); System.err.println(s1); JSONObject jsonObject = JSONObject.parseObject(s1); String string = jsonObject.getString("state"); if (StringUtils.hasLength( string)){ if (string.equals("SUCCESS")) { order.setIsWithdrawal(3); orderService.updateById(order); withdrawDetail.setStatus("SUCCESS"); withdrawDetailService.updateById(withdrawDetail); } else if (s.equals("FAIL")||s.equals("CANCELING")||s.equals("CANCELLED")) { // 重新发起一笔转账 withdrawDetail.setStatus("FAIL"); withdrawDetailService.updateById(withdrawDetail); weChatPay(order.getOrderMoney(), data.getOpenId(),withdraw.getId(),order.getServeName()); } } // /** // * 每隔一分钟去处理的定时任务 // */ // @Scheduled(fixedRate = 10000 * 60) // public void taskMinute(){ // try { // // List<WithdrawDetail> list = withdrawDetailService.lambdaQuery().ne(WithdrawDetail::getStatus, "SUCCESS").ne(WithdrawDetail::getStatus, "FAIL").list(); // // for (WithdrawDetail withdrawDetail : list) { // String s = GetTransferBatchByOutNo.checkStatus(withdrawDetail.getOutBatchNo()); // if (s.equals("SUCCESS")) { // withdrawDetail.setStatus("SUCCESS"); // withdrawDetailService.updateById(withdrawDetail); // //执行订单提现成功,增加提现成功金额 // //查询订单 // // //增加已提现金额 // // // } else if (s.equals("FAIL")) { // withdrawDetail.setStatus("FAIL"); // withdrawDetailService.updateById(withdrawDetail); // }else { // withdrawDetail.setStatus(s); // withdrawDetailService.updateById(withdrawDetail); // } // // } // // } catch (Exception e) { // e.printStackTrace(); // } // } } } catch (Exception e) { e.printStackTrace(); } } private boolean weChatPay(BigDecimal orderMoney, String openId, String withdrawId, String serverName) { if (com.ruoyi.common.core.utils.StringUtils.isBlank(openId)) { return false; } BigDecimal maxTransferAmount = new BigDecimal("200000"); // 单次转账限额,单位为分 int totalTransfers = orderMoney.multiply(new BigDecimal("100")).divide(maxTransferAmount, 0, RoundingMode.UP).intValue(); boolean allTransfersSuccessful = true; for (int i = 0; i < totalTransfers; i++) { BigDecimal transferAmount; if (i < totalTransfers - 1) { transferAmount = maxTransferAmount; } else { // 最后一笔转账,金额为剩余金额 transferAmount = orderMoney.multiply(new BigDecimal("100")).subtract(maxTransferAmount.multiply(new BigDecimal(i))).setScale(0, RoundingMode.DOWN); } Map<String, Object> postMap = new HashMap<>(8); postMap.put(WechatConstants.APP_ID, "wx98563d0ec9cf21c8"); // 订单号 String s = String.valueOf(UUID.randomUUID()).replaceAll("-", ""); postMap.put("out_bill_no", s); System.err.println("====="+postMap.get("out_bill_no")); postMap.put(WechatConstants.OPEN_ID, openId); // 转账金额 postMap.put("transfer_amount", transferAmount); // 转账备注 postMap.put("transfer_remark", "二手回收提现确认收款"); // 回调地址 postMap.put("notify_url", "https://hyhsbqgc.com/api/ruoyi-order/wx/wxChatPay"); // 转账场景报备信息 Map<String, Object> info = new HashMap<>(); info.put("info_type","回收商品名称"); info.put("info_content",serverName); postMap.put("transfer_scene_report_infos", com.alibaba.fastjson2.JSONObject.toJSONString(info)); String result = HttpUtil.postTransBatRequest( WechatConstants.WE_CHAT_PAY_URL_PRE, com.alibaba.fastjson2.JSONObject.toJSONString(postMap), "7EEA04429B006E12AAA421C002EC48BBEED5BE94", "1665330417", "/usr/local/vx/apiclient_key.pem", WechatConstants.WE_CHAT_URL_SUF); com.alibaba.fastjson2.JSONObject jsonObject = com.alibaba.fastjson2.JSONObject.parseObject(result); // WithdrawDetail withdrawDetail = new WithdrawDetail(); // withdrawDetail.setWithdrawId(withdrawId); // withdrawDetail.setMoney(transferAmount); // withdrawDetail.setOutBatchNo((String) postMap.get(WechatConstants.OUT_BATCH_NO)); // withdrawDetailService.save(withdrawDetail); if (jsonObject.containsKey(WechatConstants.CREATE_TIME)) { String string = jsonObject.getString("package_info"); Withdraw withdraw = withdrawService.getById(withdrawId); WithdrawDetail one = withdrawDetailService.lambdaQuery().eq(WithdrawDetail::getWithdrawId, withdrawId).last("limit 1").one(); if (one!=null){ one.setOutBatchNo(s); one.setStatus("PENDING"); withdrawDetailService.updateById(one); Order order = orderService.getById(withdraw.getOrderId()); order.setPackageInfo(string); order.setIsWithdrawal(2); orderService.updateById(order); } } else { allTransfersSuccessful = false; break; } } return allTransfersSuccessful; } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/vx/HttpUtil.java
@@ -67,6 +67,47 @@ } return null; } /** * 查询转账情况 * * @param requestUrl 请求路径 * @param requestJson 组合参数 * @param wechatPayserialNo 商户证书序列号 * @param privatekeypath 商户私钥证书路径 */ public static String queryTransBatRequest( String requestUrl, String wechatPayserialNo, String mchId, String privatekeypath, String url) { CloseableHttpResponse response; HttpEntity entity; CloseableHttpClient httpClient = null; try { HttpGet httpGet = createHttpGet(requestUrl, wechatPayserialNo, mchId, privatekeypath, url); httpClient = HttpClients.createDefault(); //发起转账请求 response = httpClient.execute(httpGet); log.info("response:{}", response); //获取返回的数据 entity = response.getEntity(); log.info("-----getHeaders.Request-ID:" + response.getHeaders("Request-ID")); return EntityUtils.toString(entity); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭流 try { if (httpClient != null) { httpClient.close(); } } catch (IOException e) { e.printStackTrace(); } } return null; } /**